mirror of
https://github.com/crater-invoice/crater.git
synced 2025-12-15 18:02:55 -05:00
build version 400
This commit is contained in:
233
resources/assets/js/views/settings/BackupSetting.vue
Normal file
233
resources/assets/js/views/settings/BackupSetting.vue
Normal file
@@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<div class="relative setting-main-container backup">
|
||||
<sw-card variant="setting-card">
|
||||
<div slot="header" class="flex flex-wrap justify-between lg:flex-no-wrap">
|
||||
<div>
|
||||
<h6 class="sw-section-title">
|
||||
{{ $tc('settings.backup.title', 1) }}
|
||||
</h6>
|
||||
<p
|
||||
class="mt-2 text-sm leading-snug text-gray-500"
|
||||
style="max-width: 680px"
|
||||
>
|
||||
{{ $t('settings.backup.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 lg:mt-0 lg:ml-2">
|
||||
<sw-button
|
||||
variant="primary-outline"
|
||||
size="lg"
|
||||
@click="onCreateNewBackup"
|
||||
>
|
||||
<plus-icon class="w-6 h-6 mr-1 -ml-2" />
|
||||
{{ $t('settings.backup.new_backup') }}
|
||||
</sw-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid mb-8 md:grid-cols-3">
|
||||
<sw-input-group :label="$t('settings.disk.select_disk')">
|
||||
<sw-select
|
||||
v-model="filters.selected_disk"
|
||||
:options="getDisks"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:placeholder="$t('settings.disk.select_disk')"
|
||||
:allow-empty="false"
|
||||
track-by="id"
|
||||
label="name"
|
||||
:custom-label="getCustomLabel"
|
||||
@select="refreshTable"
|
||||
/>
|
||||
</sw-input-group>
|
||||
</div>
|
||||
<sw-table-component
|
||||
ref="table"
|
||||
variant="gray"
|
||||
:show-filter="false"
|
||||
:data="fetchBackupsData"
|
||||
>
|
||||
<sw-table-column :label="$t('settings.backup.path')" show="path">
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.backup.path') }}</span>
|
||||
<span class="mt-6">{{ row.path }}</span>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
<sw-table-column
|
||||
:label="$t('settings.backup.created_at')"
|
||||
show="created_at"
|
||||
/>
|
||||
<sw-table-column :label="$t('settings.backup.size')" show="size" />
|
||||
<sw-table-column
|
||||
:sortable="false"
|
||||
:filterable="false"
|
||||
:data="fetchBackupsData"
|
||||
cell-class="action-dropdown"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.backup.action') }}</span>
|
||||
<sw-dropdown>
|
||||
<dot-icon slot="activator" />
|
||||
|
||||
<sw-dropdown-item @click="onDownloadBckup(row)">
|
||||
<cloud-download-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('general.download') }}
|
||||
</sw-dropdown-item>
|
||||
<sw-dropdown-item @click="onRemoveBackup(row)">
|
||||
<trash-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('general.delete') }}
|
||||
</sw-dropdown-item>
|
||||
</sw-dropdown>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
</sw-table-component>
|
||||
</sw-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import { TrashIcon, CloudDownloadIcon, PlusIcon } from '@vue-hero-icons/solid'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PlusIcon,
|
||||
TrashIcon,
|
||||
CloudDownloadIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isRequestOngoing: true,
|
||||
|
||||
filters: {
|
||||
selected_disk: { driver: 'local' },
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters('disks', ['getDisks']),
|
||||
},
|
||||
|
||||
created() {
|
||||
this.loadDisksData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('backup', ['fetchBackups', 'downloadBackup', 'removeBackup']),
|
||||
|
||||
...mapActions('disks', ['fetchDisks']),
|
||||
|
||||
...mapActions('modal', ['openModal']),
|
||||
|
||||
getCustomLabel({ driver, name }) {
|
||||
if (!name) {
|
||||
return
|
||||
}
|
||||
return `${name} — [${driver}]`
|
||||
},
|
||||
|
||||
async onRemoveBackup(backup) {
|
||||
swal({
|
||||
title: this.$t('general.are_you_sure'),
|
||||
text: this.$t('settings.backup.backup_confirm_delete'),
|
||||
icon: '/assets/icon/trash-solid.svg',
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
}).then(async (value) => {
|
||||
if (value) {
|
||||
let data = {
|
||||
disk: this.filters.selected_disk.driver,
|
||||
file_disk_id: this.filters.selected_disk.id,
|
||||
path: backup.path,
|
||||
}
|
||||
let response = await this.removeBackup(data)
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](this.$t('settings.backup.deleted_message'))
|
||||
this.$refs.table.refresh()
|
||||
return true
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async loadDisksData() {
|
||||
this.isRequestOngoing = true
|
||||
|
||||
let res = await this.fetchDisks({ limit: 'all' })
|
||||
this.filters.selected_disk = res.data.disks.data.find(
|
||||
(disk) => disk.set_as_default == 1
|
||||
)
|
||||
this.isRequestOngoing = false
|
||||
},
|
||||
|
||||
async fetchBackupsData({ page, filter, sort }) {
|
||||
let data = {
|
||||
disk: this.filters.selected_disk.driver,
|
||||
file_disk_id: this.filters.selected_disk.id,
|
||||
}
|
||||
|
||||
this.isRequestOngoing = true
|
||||
let response = await this.fetchBackups(data)
|
||||
|
||||
if (response.data.error) {
|
||||
window.toastr['error'](
|
||||
this.$t('settings.backup.' + response.data.error)
|
||||
)
|
||||
}
|
||||
|
||||
this.isRequestOngoing = false
|
||||
|
||||
return {
|
||||
data: response.data.backups,
|
||||
pagination: {
|
||||
totalPages: 1,
|
||||
currentPage: 1,
|
||||
},
|
||||
}
|
||||
this.$refs.table.refresh()
|
||||
},
|
||||
|
||||
refreshTable() {
|
||||
setTimeout(() => {
|
||||
this.$refs.table.refresh()
|
||||
}, 100)
|
||||
},
|
||||
|
||||
async onCreateNewBackup() {
|
||||
this.openModal({
|
||||
title: this.$t('settings.backup.create_backup'),
|
||||
componentName: 'BackupModal',
|
||||
refreshData: this.refreshTable,
|
||||
})
|
||||
},
|
||||
|
||||
async onDownloadBckup(backup) {
|
||||
this.isRequestOngoing = true
|
||||
window
|
||||
.axios({
|
||||
method: 'GET',
|
||||
url: '/api/v1/download-backup',
|
||||
responseType: 'blob', // important
|
||||
params: {
|
||||
disk: this.filters.selected_disk.driver,
|
||||
file_disk_id: this.filters.selected_disk.id,
|
||||
path: backup.path,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
const url = window.URL.createObjectURL(new Blob([response.data]))
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', backup.path.split('/')[1])
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
this.isRequestOngoing = false
|
||||
})
|
||||
.catch((e) => {
|
||||
this.isRequestOngoing = false
|
||||
window.toastr['error'](e.response.data.message)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,278 +0,0 @@
|
||||
<template>
|
||||
<div class="setting-main-container">
|
||||
<form action="" @submit.prevent="updateCompany">
|
||||
<div class="card setting-card">
|
||||
<div class="page-header">
|
||||
<h3 class="page-title">{{ $t('settings.company_info.company_info') }}</h3>
|
||||
<p class="page-sub-title">
|
||||
{{ $t('settings.company_info.section_description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="input-label">{{ $tc('settings.company_info.company_logo') }}</label>
|
||||
<div id="pick-avatar" class="image-upload-box">
|
||||
<div class="overlay">
|
||||
<font-awesome-icon class="white-icon" icon="camera"/>
|
||||
</div>
|
||||
<img v-if="previewLogo" :src="previewLogo" class="preview-logo">
|
||||
<div v-else class="upload-content">
|
||||
<font-awesome-icon class="upload-icon" icon="cloud-upload-alt"/>
|
||||
<p class="upload-text"> {{ $tc('general.choose_file') }} </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<avatar-cropper
|
||||
:labels="{ submit: 'Submit', cancel: 'Cancel'}"
|
||||
:cropper-options="cropperOptions"
|
||||
:output-options="cropperOutputOptions"
|
||||
:output-quality="0.8"
|
||||
:upload-handler="cropperHandler"
|
||||
trigger="#pick-avatar"
|
||||
@changed="setFileObject"
|
||||
@error="handleUploadError"
|
||||
/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4">
|
||||
<label class="input-label">{{ $tc('settings.company_info.company_name') }}</label> <span class="text-danger"> * </span>
|
||||
<base-input
|
||||
v-model="formData.name"
|
||||
:invalid="$v.formData.name.$error"
|
||||
:placeholder="$t('settings.company_info.company_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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<label class="input-label">{{ $tc('settings.company_info.phone') }}</label>
|
||||
<base-input
|
||||
v-model="formData.phone"
|
||||
:placeholder="$t('settings.company_info.phone')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<label class="input-label">{{ $tc('settings.company_info.country') }}</label><span class="text-danger"> * </span>
|
||||
<base-select
|
||||
v-model="country"
|
||||
:options="countries"
|
||||
:class="{'error': $v.formData.country_id.$error }"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:allow-empty="false"
|
||||
:placeholder="$t('general.select_country')"
|
||||
label="name"
|
||||
track-by="id"
|
||||
/>
|
||||
<div v-if="$v.formData.country_id.$error">
|
||||
<span v-if="!$v.formData.country_id.required" class="text-danger">{{ $tc('validation.required') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<label class="input-label">{{ $tc('settings.company_info.state') }}</label>
|
||||
<base-input
|
||||
v-model="formData.state"
|
||||
:placeholder="$tc('settings.company_info.state')"
|
||||
name="state"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<label class="input-label">{{ $tc('settings.company_info.city') }}</label>
|
||||
<base-input
|
||||
v-model="formData.city"
|
||||
:placeholder="$tc('settings.company_info.city')"
|
||||
name="city"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<label class="input-label">{{ $tc('settings.company_info.zip') }}</label>
|
||||
<base-input
|
||||
v-model="formData.zip"
|
||||
:placeholder="$tc('settings.company_info.zip')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<label class="input-label">{{ $tc('settings.company_info.address') }}</label>
|
||||
<base-text-area
|
||||
v-model="formData.address_street_1"
|
||||
:placeholder="$tc('general.street_1')"
|
||||
:class="{'invalid': $v.formData.address_street_1.$error }"
|
||||
rows="2"
|
||||
@input="$v.formData.address_street_1.$touch()"
|
||||
/>
|
||||
<div v-if="$v.formData.address_street_1.$error">
|
||||
<span v-if="!$v.formData.address_street_1.maxLength" class="text-danger">{{ $tc('validation.address_maxlength') }}</span>
|
||||
</div>
|
||||
<base-text-area
|
||||
v-model="formData.address_street_2"
|
||||
:placeholder="$tc('general.street_2')"
|
||||
:class="{'invalid': $v.formData.address_street_2.$error }"
|
||||
rows="2"
|
||||
@input="$v.formData.address_street_2.$touch()"
|
||||
/>
|
||||
<div v-if="$v.formData.address_street_2.$error">
|
||||
<span v-if="!$v.formData.address_street_2.maxLength" class="text-danger">{{ $tc('validation.address_maxlength') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<base-button
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
icon="save"
|
||||
color="theme"
|
||||
type="submit"
|
||||
>
|
||||
{{ $tc('settings.company_info.save') }}
|
||||
</base-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import IconUpload from '../../components/icon/upload'
|
||||
import ImageBox from '../components/ImageBox.vue'
|
||||
import AvatarCropper from 'vue-avatar-cropper'
|
||||
import { validationMixin } from 'vuelidate'
|
||||
import { mapActions } from 'vuex'
|
||||
const { required, email, maxLength } = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
components: { AvatarCropper, IconUpload, ImageBox },
|
||||
mixins: [validationMixin],
|
||||
data () {
|
||||
return {
|
||||
cropperOutputOptions: {
|
||||
width: 150,
|
||||
height: 150
|
||||
},
|
||||
cropperOptions: {
|
||||
autoCropArea: 1,
|
||||
viewMode: 0,
|
||||
movable: true,
|
||||
zoomable: true
|
||||
},
|
||||
isFetchingData: false,
|
||||
formData: {
|
||||
name: null,
|
||||
email: '',
|
||||
phone: '',
|
||||
zip: '',
|
||||
address_street_1: '',
|
||||
address_street_2: '',
|
||||
website: '',
|
||||
country_id: null,
|
||||
state: '',
|
||||
city: ''
|
||||
},
|
||||
isLoading: false,
|
||||
isHidden: false,
|
||||
country: null,
|
||||
previewLogo: null,
|
||||
countries: [],
|
||||
passData: [],
|
||||
fileSendUrl: '/api/settings/company',
|
||||
fileObject: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
country (newCountry) {
|
||||
this.formData.country_id = newCountry.id
|
||||
if (this.isFetchingData) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
formData: {
|
||||
name: {
|
||||
required
|
||||
},
|
||||
country_id: {
|
||||
required
|
||||
},
|
||||
email: {
|
||||
email
|
||||
},
|
||||
address_street_1: {
|
||||
maxLength: maxLength(255)
|
||||
},
|
||||
address_street_2: {
|
||||
maxLength: maxLength(255)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.fetchCountry()
|
||||
this.setInitialData()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('companyInfo', [
|
||||
'loadData',
|
||||
'editCompany',
|
||||
'getFile'
|
||||
]),
|
||||
cropperHandler (cropper) {
|
||||
this.previewLogo = cropper.getCroppedCanvas().toDataURL(this.cropperOutputMime)
|
||||
},
|
||||
setFileObject (file) {
|
||||
this.fileObject = file
|
||||
},
|
||||
handleUploadError (message, type, xhr) {
|
||||
window.toastr['error']('Oops! Something went wrong...')
|
||||
},
|
||||
async setInitialData () {
|
||||
let response = await this.loadData()
|
||||
this.isFetchingData = true
|
||||
this.formData.name = response.data.user.company.name
|
||||
this.formData.address_street_1 = response.data.user.addresses[0].address_street_1
|
||||
this.formData.address_street_2 = response.data.user.addresses[0].address_street_2
|
||||
this.formData.zip = response.data.user.addresses[0].zip
|
||||
this.formData.phone = response.data.user.addresses[0].phone
|
||||
this.formData.state = response.data.user.addresses[0].state
|
||||
this.formData.city = response.data.user.addresses[0].city
|
||||
this.country = response.data.user.addresses[0].country
|
||||
this.previewLogo = response.data.user.company.logo
|
||||
},
|
||||
async updateCompany () {
|
||||
this.$v.formData.$touch()
|
||||
if (this.$v.$invalid) {
|
||||
return true
|
||||
}
|
||||
this.isLoading = true
|
||||
|
||||
let response = await this.editCompany(this.formData)
|
||||
if (response.data.success) {
|
||||
this.isLoading = false
|
||||
if (this.fileObject && this.previewLogo) {
|
||||
let logoData = new FormData()
|
||||
logoData.append('company_logo', JSON.stringify({
|
||||
name: this.fileObject.name,
|
||||
data: this.previewLogo
|
||||
}))
|
||||
await axios.post('/api/settings/company/upload-logo', logoData)
|
||||
}
|
||||
this.isLoading = false
|
||||
window.toastr['success'](this.$t('settings.company_info.updated_message'))
|
||||
return true
|
||||
}
|
||||
this.isLoading = false
|
||||
window.toastr['error'](response.data.error)
|
||||
return true
|
||||
},
|
||||
async fetchCountry () {
|
||||
let res = await window.axios.get('/api/countries')
|
||||
if (res) {
|
||||
this.countries = res.data.countries
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
336
resources/assets/js/views/settings/CompanyInfoSetting.vue
Normal file
336
resources/assets/js/views/settings/CompanyInfoSetting.vue
Normal file
@@ -0,0 +1,336 @@
|
||||
<template>
|
||||
<form @submit.prevent="updateCompanyData" class="relative h-full">
|
||||
<base-loader v-if="isRequestOnGoing" :show-bg-overlay="true" />
|
||||
<sw-card variant="setting-card">
|
||||
<template slot="header">
|
||||
<h6 class="sw-section-title">
|
||||
{{ $t('settings.company_info.company_info') }}
|
||||
</h6>
|
||||
<p
|
||||
class="mt-2 text-sm leading-snug text-gray-500"
|
||||
style="max-width: 680px"
|
||||
>
|
||||
{{ $t('settings.company_info.section_description') }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<div class="grid mb-6 md:grid-cols-2">
|
||||
<sw-input-group :label="$tc('settings.company_info.company_logo')">
|
||||
<div
|
||||
id="logo-box"
|
||||
class="relative flex items-center justify-center h-24 p-5 mt-2 bg-transparent border-2 border-gray-200 border-dashed rounded-md image-upload-box"
|
||||
>
|
||||
<img
|
||||
v-if="previewLogo"
|
||||
:src="previewLogo"
|
||||
class="absolute opacity-100 preview-logo"
|
||||
style="max-height: 80%; animation: fadeIn 2s ease"
|
||||
/>
|
||||
<div v-else class="flex flex-col items-center">
|
||||
<cloud-upload-icon
|
||||
class="h-5 mb-2 text-xl leading-6 text-gray-400"
|
||||
/>
|
||||
<p class="text-xs leading-4 text-center text-gray-400">
|
||||
Drag a file here or
|
||||
<span id="pick-avatar" class="cursor-pointer text-primary-500">
|
||||
browse
|
||||
</span>
|
||||
to choose a file
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<sw-avatar
|
||||
trigger="#logo-box"
|
||||
:preview-avatar="previewLogo"
|
||||
@changed="onChange"
|
||||
@uploadHandler="onUploadHandler"
|
||||
@handleUploadError="onHandleUploadError"
|
||||
>
|
||||
<template v-slot:icon>
|
||||
<cloud-upload-icon
|
||||
class="h-5 mb-2 text-xl leading-6 text-gray-400"
|
||||
/>
|
||||
</template>
|
||||
</sw-avatar>
|
||||
</sw-input-group>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 sm:grid-col-1 md:grid-cols-2">
|
||||
<sw-input-group
|
||||
:label="$tc('settings.company_info.company_name')"
|
||||
:error="nameError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
v-model="formData.name"
|
||||
:invalid="$v.formData.name.$error"
|
||||
:placeholder="$t('settings.company_info.company_name')"
|
||||
class="mt-2"
|
||||
@input="$v.formData.name.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group :label="$tc('settings.company_info.phone')">
|
||||
<sw-input
|
||||
v-model="formData.phone"
|
||||
class="mt-2"
|
||||
:placeholder="$t('settings.company_info.phone')"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$tc('settings.company_info.country')"
|
||||
:error="countryError"
|
||||
required
|
||||
>
|
||||
<sw-select
|
||||
v-model="country"
|
||||
:options="countries"
|
||||
:class="{ error: $v.formData.country_id.$error }"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:allow-empty="false"
|
||||
:placeholder="$t('general.select_country')"
|
||||
class="mt-2"
|
||||
label="name"
|
||||
track-by="id"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group :label="$tc('settings.company_info.state')">
|
||||
<sw-input
|
||||
v-model="formData.state"
|
||||
:placeholder="$tc('settings.company_info.state')"
|
||||
name="state"
|
||||
class="mt-2"
|
||||
type="text"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group :label="$tc('settings.company_info.city')">
|
||||
<sw-input
|
||||
v-model="formData.city"
|
||||
:placeholder="$tc('settings.company_info.city')"
|
||||
name="city"
|
||||
class="mt-2"
|
||||
type="text"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group :label="$tc('settings.company_info.zip')">
|
||||
<sw-input
|
||||
v-model="formData.zip"
|
||||
:placeholder="$tc('settings.company_info.zip')"
|
||||
class="mt-2"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<div>
|
||||
<sw-input-group
|
||||
:label="$tc('settings.company_info.address')"
|
||||
:error="address1Error"
|
||||
>
|
||||
<sw-textarea
|
||||
v-model="formData.address_street_1"
|
||||
:placeholder="$tc('general.street_1')"
|
||||
:class="{ invalid: $v.formData.address_street_1.$error }"
|
||||
rows="2"
|
||||
@input="$v.formData.address_street_1.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group :error="address2Error" class="my-2">
|
||||
<sw-textarea
|
||||
v-model="formData.address_street_2"
|
||||
:placeholder="$tc('general.street_2')"
|
||||
:class="{ invalid: $v.formData.address_street_2.$error }"
|
||||
rows="2"
|
||||
@input="$v.formData.address_street_2.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<sw-button
|
||||
class="mt-4"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
variant="primary"
|
||||
>
|
||||
<save-icon v-if="!isLoading" class="mr-2 -ml-1" />
|
||||
{{ $tc('settings.company_info.save') }}
|
||||
</sw-button>
|
||||
</sw-card>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import { CloudUploadIcon } from '@vue-hero-icons/solid'
|
||||
const { required, email, maxLength } = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CloudUploadIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isFetchingData: false,
|
||||
formData: {
|
||||
name: null,
|
||||
email: '',
|
||||
phone: '',
|
||||
zip: '',
|
||||
address_street_1: '',
|
||||
address_street_2: '',
|
||||
website: '',
|
||||
country_id: null,
|
||||
state: '',
|
||||
city: '',
|
||||
},
|
||||
isLoading: false,
|
||||
country: null,
|
||||
passData: [],
|
||||
fileSendUrl: '/api/v1/settings/company',
|
||||
previewLogo: null,
|
||||
fileObject: null,
|
||||
cropperOutputMime: '',
|
||||
isRequestOnGoing: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
country(newCountry) {
|
||||
this.formData.country_id = newCountry.id
|
||||
if (this.isFetchingData) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
},
|
||||
validations: {
|
||||
formData: {
|
||||
name: {
|
||||
required,
|
||||
},
|
||||
country_id: {
|
||||
required,
|
||||
},
|
||||
email: {
|
||||
email,
|
||||
},
|
||||
address_street_1: {
|
||||
maxLength: maxLength(255),
|
||||
},
|
||||
address_street_2: {
|
||||
maxLength: maxLength(255),
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['countries']),
|
||||
nameError() {
|
||||
if (!this.$v.formData.name.$error) {
|
||||
return ''
|
||||
}
|
||||
if (!this.$v.formData.name.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
countryError() {
|
||||
if (!this.$v.formData.country_id.$error) {
|
||||
return ''
|
||||
}
|
||||
if (!this.$v.formData.country_id.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
address1Error() {
|
||||
if (!this.$v.formData.address_street_1.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.formData.address_street_1.maxLength) {
|
||||
return this.$tc('validation.address_maxlength')
|
||||
}
|
||||
},
|
||||
address2Error() {
|
||||
if (!this.$v.formData.address_street_2.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.formData.address_street_2.maxLength) {
|
||||
return this.$tc('validation.address_maxlength')
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.setInitialData()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('company', ['updateCompany', 'updateCompanyLogo']),
|
||||
...mapActions('user', ['fetchCurrentUser']),
|
||||
onUploadHandler(cropper) {
|
||||
this.previewLogo = cropper
|
||||
.getCroppedCanvas()
|
||||
.toDataURL(this.cropperOutputMime)
|
||||
},
|
||||
onHandleUploadError() {
|
||||
window.toastr['error']('Oops! Something went wrong...')
|
||||
},
|
||||
onChange(file) {
|
||||
this.cropperOutputMime = file.type
|
||||
this.fileObject = file
|
||||
},
|
||||
async setInitialData() {
|
||||
this.isRequestOnGoing = true
|
||||
let response = await this.fetchCurrentUser()
|
||||
this.isFetchingData = true
|
||||
if (response.data.user) {
|
||||
this.formData.name = response.data.user.company.name
|
||||
this.formData.address_street_1 =
|
||||
response.data.user.company.address.address_street_1
|
||||
this.formData.address_street_2 =
|
||||
response.data.user.company.address.address_street_2
|
||||
this.formData.zip = response.data.user.company.address.zip
|
||||
this.formData.phone = response.data.user.company.address.phone
|
||||
this.formData.state = response.data.user.company.address.state
|
||||
this.formData.city = response.data.user.company.address.city
|
||||
this.country = response.data.user.company.address.country
|
||||
this.previewLogo = response.data.user.company.logo
|
||||
}
|
||||
this.isRequestOnGoing = false
|
||||
},
|
||||
async updateCompanyData() {
|
||||
this.$v.formData.$touch()
|
||||
if (this.$v.$invalid) {
|
||||
return true
|
||||
}
|
||||
this.isLoading = true
|
||||
|
||||
let response = await this.updateCompany(this.formData)
|
||||
if (response.data.success) {
|
||||
this.isLoading = false
|
||||
if (this.fileObject && this.previewLogo) {
|
||||
let logoData = new FormData()
|
||||
logoData.append(
|
||||
'company_logo',
|
||||
JSON.stringify({
|
||||
name: this.fileObject.name,
|
||||
data: this.previewLogo,
|
||||
})
|
||||
)
|
||||
await this.updateCompanyLogo(logoData)
|
||||
}
|
||||
this.isLoading = false
|
||||
window.toastr['success'](
|
||||
this.$t('settings.company_info.updated_message')
|
||||
)
|
||||
return true
|
||||
}
|
||||
this.isLoading = false
|
||||
window.toastr['error'](response.data.error)
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
183
resources/assets/js/views/settings/CustomFieldsSetting.vue
Normal file
183
resources/assets/js/views/settings/CustomFieldsSetting.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<sw-card variant="setting-card">
|
||||
<div slot="header" class="flex flex-wrap justify-between lg:flex-no-wrap">
|
||||
<div>
|
||||
<h6 class="sw-section-title">
|
||||
{{ $t('settings.menu_title.custom_fields') }}
|
||||
</h6>
|
||||
<p
|
||||
class="mt-2 text-sm leading-snug text-gray-500"
|
||||
style="max-width: 680px"
|
||||
>
|
||||
{{ $t('settings.custom_fields.section_description') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 lg:mt-0 lg:ml-2">
|
||||
<sw-button variant="primary-outline" size="lg" @click="addCustomField">
|
||||
<plus-icon class="w-6 h-6 mr-1 -ml-2" />
|
||||
{{ $t('settings.custom_fields.add_custom_field') }}
|
||||
</sw-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<sw-table-component
|
||||
ref="table"
|
||||
variant="gray"
|
||||
:show-filter="false"
|
||||
:data="fetchData"
|
||||
>
|
||||
<sw-table-column
|
||||
:sortable="true"
|
||||
:label="$t('settings.custom_fields.name')"
|
||||
show="name"
|
||||
/>
|
||||
|
||||
<sw-table-column
|
||||
:sortable="true"
|
||||
:label="$t('settings.custom_fields.label')"
|
||||
show="label"
|
||||
/>
|
||||
|
||||
<sw-table-column
|
||||
:sortable="true"
|
||||
:label="$t('settings.custom_fields.model')"
|
||||
show="model_type"
|
||||
/>
|
||||
|
||||
<sw-table-column
|
||||
:sortable="true"
|
||||
:label="$t('settings.custom_fields.type')"
|
||||
show="type.label"
|
||||
/>
|
||||
|
||||
<sw-table-column
|
||||
:sortable="true"
|
||||
:filterable="true"
|
||||
:label="$t('settings.custom_fields.required')"
|
||||
show="is_required"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.custom_fields.required') }}</span>
|
||||
<sw-badge
|
||||
:bg-color="
|
||||
$utils.getBadgeStatusColor(row.is_required ? 'YES' : 'NO').bgColor
|
||||
"
|
||||
:color="
|
||||
$utils.getBadgeStatusColor(row.is_required ? 'YES' : 'NO').color
|
||||
"
|
||||
>
|
||||
{{
|
||||
row.is_required
|
||||
? $t('settings.custom_fields.yes')
|
||||
: $t('settings.custom_fields.no').replace('_', ' ')
|
||||
}}
|
||||
</sw-badge>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
|
||||
<sw-table-column
|
||||
:sortable="false"
|
||||
:filterable="false"
|
||||
cell-class="action-dropdown"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.tax_types.action') }}</span>
|
||||
<sw-dropdown>
|
||||
<dot-icon slot="activator" />
|
||||
|
||||
<sw-dropdown-item @click="editCustomField(row.id)">
|
||||
<pencil-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('general.edit') }}
|
||||
</sw-dropdown-item>
|
||||
|
||||
<sw-dropdown-item @click="removeCustomField(row.id)">
|
||||
<trash-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('general.delete') }}
|
||||
</sw-dropdown-item>
|
||||
</sw-dropdown>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
</sw-table-component>
|
||||
</sw-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import { PencilIcon, TrashIcon, PlusIcon } from '@vue-hero-icons/solid'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PencilIcon,
|
||||
TrashIcon,
|
||||
PlusIcon,
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('customFields', ['fetchCustomFields', 'deleteCustomFields']),
|
||||
|
||||
...mapActions('modal', ['openModal']),
|
||||
|
||||
async fetchData({ page, filter, sort }) {
|
||||
let data = {
|
||||
orderByField: sort.fieldName || 'created_at',
|
||||
orderBy: sort.order || 'desc',
|
||||
page,
|
||||
}
|
||||
|
||||
let response = await this.fetchCustomFields(data)
|
||||
|
||||
return {
|
||||
data: response.data.customFields.data,
|
||||
pagination: {
|
||||
totalPages: response.data.customFields.last_page,
|
||||
currentPage: page,
|
||||
count: response.data.customFields.count,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
async removeCustomField(id) {
|
||||
swal({
|
||||
title: this.$t('general.are_you_sure'),
|
||||
text: this.$t('settings.custom_fields.custom_field_confirm_delete'),
|
||||
icon: '/assets/icon/trash-solid.svg',
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
}).then(async (value) => {
|
||||
if (value) {
|
||||
let response = await this.deleteCustomFields(id)
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](
|
||||
this.$t('settings.custom_fields.deleted_message')
|
||||
)
|
||||
this.id = null
|
||||
this.$refs.table.refresh()
|
||||
return true
|
||||
}
|
||||
window.toastr['error'](
|
||||
this.$t('settings.custom_fields.already_in_use')
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
addCustomField() {
|
||||
this.openModal({
|
||||
title: this.$t('settings.custom_fields.add_custom_field'),
|
||||
componentName: 'CustomFieldModal',
|
||||
refreshData: this.$refs.table.refresh,
|
||||
})
|
||||
},
|
||||
|
||||
editCustomField(id) {
|
||||
this.openModal({
|
||||
title: this.$t('settings.custom_fields.edit_custom_field'),
|
||||
componentName: 'CustomFieldModal',
|
||||
id: id,
|
||||
refreshData: this.$refs.table.refresh,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,599 +0,0 @@
|
||||
<template>
|
||||
<div class="setting-main-container customization">
|
||||
<div class="card setting-card">
|
||||
<ul class="tabs">
|
||||
<li class="tab" @click="setActiveTab('INVOICES')">
|
||||
<a :class="['tab-link', {'a-active': activeTab === 'INVOICES'}]" href="#">{{ $t('settings.customization.invoices.title') }}</a>
|
||||
</li>
|
||||
<li class="tab" @click="setActiveTab('ESTIMATES')">
|
||||
<a :class="['tab-link', {'a-active': activeTab === 'ESTIMATES'}]" href="#">{{ $t('settings.customization.estimates.title') }}</a>
|
||||
</li>
|
||||
<li class="tab" @click="setActiveTab('PAYMENTS')">
|
||||
<a :class="['tab-link', {'a-active': activeTab === 'PAYMENTS'}]" href="#">{{ $t('settings.customization.payments.title') }}</a>
|
||||
</li>
|
||||
<li class="tab" @click="setActiveTab('ITEMS')">
|
||||
<a :class="['tab-link', {'a-active': activeTab === 'ITEMS'}]" href="#">{{ $t('settings.customization.items.title') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Invoices Tab -->
|
||||
<transition name="fade-customize">
|
||||
<div v-if="activeTab === 'INVOICES'" class="invoice-tab">
|
||||
<form action="" class="mt-3" @submit.prevent="updateInvoiceSetting">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-4">
|
||||
<label class="input-label">{{ $t('settings.customization.invoices.invoice_prefix') }}</label>
|
||||
<base-input
|
||||
v-model="invoices.invoice_prefix"
|
||||
:invalid="$v.invoices.invoice_prefix.$error"
|
||||
class="prefix-input"
|
||||
@input="$v.invoices.invoice_prefix.$touch()"
|
||||
@keyup="changeToUppercase('INVOICES')"
|
||||
/>
|
||||
<span v-show="!$v.invoices.invoice_prefix.required" class="text-danger mt-1">{{ $t('validation.required') }}</span>
|
||||
<span v-if="!$v.invoices.invoice_prefix.maxLength" class="text-danger">{{ $t('validation.prefix_maxlength') }}</span>
|
||||
<span v-if="!$v.invoices.invoice_prefix.alpha" class="text-danger">{{ $t('validation.characters_only') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pb-3">
|
||||
<div class="col-md-12">
|
||||
<base-button
|
||||
icon="save"
|
||||
color="theme"
|
||||
type="submit"
|
||||
>
|
||||
{{ $t('settings.customization.save') }}
|
||||
</base-button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
<div class="page-header pt-3">
|
||||
<h3 class="page-title">
|
||||
{{ $t('settings.customization.invoices.invoice_settings') }}
|
||||
</h3>
|
||||
<div class="flex-box">
|
||||
<div class="left">
|
||||
<base-switch
|
||||
v-model="invoiceAutogenerate"
|
||||
class="btn-switch"
|
||||
@change="setInvoiceSetting"
|
||||
/>
|
||||
</div>
|
||||
<div class="right ml-15">
|
||||
<p class="box-title"> {{ $t('settings.customization.invoices.autogenerate_invoice_number') }} </p>
|
||||
<p class="box-desc"> {{ $t('settings.customization.invoices.invoice_setting_description') }} </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- Estimates Tab -->
|
||||
<transition name="fade-customize">
|
||||
<div v-if="activeTab === 'ESTIMATES'" class="estimate-tab">
|
||||
<form action="" class="mt-3" @submit.prevent="updateEstimateSetting">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-4">
|
||||
<label class="input-label">{{ $t('settings.customization.estimates.estimate_prefix') }}</label>
|
||||
<base-input
|
||||
v-model="estimates.estimate_prefix"
|
||||
:invalid="$v.estimates.estimate_prefix.$error"
|
||||
class="prefix-input"
|
||||
@input="$v.estimates.estimate_prefix.$touch()"
|
||||
@keyup="changeToUppercase('ESTIMATES')"
|
||||
/>
|
||||
<span v-show="!$v.estimates.estimate_prefix.required" class="text-danger mt-1">{{ $t('validation.required') }}</span>
|
||||
<span v-if="!$v.estimates.estimate_prefix.maxLength" class="text-danger">{{ $t('validation.prefix_maxlength') }}</span>
|
||||
<span v-if="!$v.estimates.estimate_prefix.alpha" class="text-danger">{{ $t('validation.characters_only') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pb-3">
|
||||
<div class="col-md-12">
|
||||
<base-button
|
||||
icon="save"
|
||||
color="theme"
|
||||
type="submit"
|
||||
>
|
||||
{{ $t('settings.customization.save') }}
|
||||
</base-button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</form>
|
||||
<div class="page-header pt-3">
|
||||
<h3 class="page-title">
|
||||
{{ $t('settings.customization.estimates.estimate_settings') }}
|
||||
</h3>
|
||||
<div class="flex-box">
|
||||
<div class="left">
|
||||
<base-switch
|
||||
v-model="estimateAutogenerate"
|
||||
class="btn-switch"
|
||||
@change="setEstimateSetting"
|
||||
/>
|
||||
</div>
|
||||
<div class="right ml-15">
|
||||
<p class="box-title"> {{ $t('settings.customization.estimates.autogenerate_estimate_number') }} </p>
|
||||
<p class="box-desc"> {{ $t('settings.customization.estimates.estimate_setting_description') }} </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- Payments Tab -->
|
||||
<transition name="fade-customize">
|
||||
<div v-if="activeTab === 'PAYMENTS'" class="payment-tab">
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<!-- <h3 class="page-title">
|
||||
{{ $t('settings.customization.payments.payment_mode') }}
|
||||
</h3> -->
|
||||
</div>
|
||||
<div class="col-md-4 d-flex flex-row-reverse">
|
||||
<base-button
|
||||
outline
|
||||
class="add-new-tax"
|
||||
color="theme"
|
||||
@click="addPaymentMode"
|
||||
>
|
||||
{{ $t('settings.customization.payments.add_payment_mode') }}
|
||||
</base-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table-component
|
||||
ref="table"
|
||||
:show-filter="false"
|
||||
:data="paymentModes"
|
||||
table-class="table tax-table"
|
||||
class="mb-3"
|
||||
>
|
||||
<table-column
|
||||
:sortable="true"
|
||||
:label="$t('settings.customization.payments.payment_mode')"
|
||||
show="name"
|
||||
/>
|
||||
<table-column
|
||||
:sortable="false"
|
||||
:filterable="false"
|
||||
cell-class="action-dropdown"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.tax_types.action') }}</span>
|
||||
<v-dropdown>
|
||||
<a slot="activator" href="#">
|
||||
<dot-icon />
|
||||
</a>
|
||||
<v-dropdown-item>
|
||||
<div class="dropdown-item" @click="editPaymentMode(row)">
|
||||
<font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon" />
|
||||
{{ $t('general.edit') }}
|
||||
</div>
|
||||
</v-dropdown-item>
|
||||
<v-dropdown-item>
|
||||
<div class="dropdown-item" @click="removePaymentMode(row.id)">
|
||||
<font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" />
|
||||
{{ $t('general.delete') }}
|
||||
</div>
|
||||
</v-dropdown-item>
|
||||
</v-dropdown>
|
||||
</template>
|
||||
</table-column>
|
||||
</table-component>
|
||||
<hr>
|
||||
<form action="" class="pt-3" @submit.prevent="updatePaymentSetting">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-4">
|
||||
<label class="input-label">{{ $t('settings.customization.payments.payment_prefix') }}</label>
|
||||
<base-input
|
||||
v-model="payments.payment_prefix"
|
||||
:invalid="$v.payments.payment_prefix.$error"
|
||||
class="prefix-input"
|
||||
@input="$v.payments.payment_prefix.$touch()"
|
||||
@keyup="changeToUppercase('PAYMENTS')"
|
||||
/>
|
||||
<span v-show="!$v.payments.payment_prefix.required" class="text-danger mt-1">{{ $t('validation.required') }}</span>
|
||||
<span v-if="!$v.payments.payment_prefix.maxLength" class="text-danger">{{ $t('validation.prefix_maxlength') }}</span>
|
||||
<span v-if="!$v.payments.payment_prefix.alpha" class="text-danger">{{ $t('validation.characters_only') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pb-3">
|
||||
<div class="col-md-12">
|
||||
<base-button
|
||||
icon="save"
|
||||
color="theme"
|
||||
type="submit"
|
||||
>
|
||||
{{ $t('settings.customization.save') }}
|
||||
</base-button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
<div class="page-header pt-3">
|
||||
<h3 class="page-title">
|
||||
{{ $t('settings.customization.payments.payment_settings') }}
|
||||
</h3>
|
||||
<div class="flex-box">
|
||||
<div class="left">
|
||||
<base-switch
|
||||
v-model="paymentAutogenerate"
|
||||
class="btn-switch"
|
||||
@change="setPaymentSetting"
|
||||
/>
|
||||
</div>
|
||||
<div class="right ml-15">
|
||||
<p class="box-title"> {{ $t('settings.customization.payments.autogenerate_payment_number') }} </p>
|
||||
<p class="box-desc"> {{ $t('settings.customization.payments.payment_setting_description') }} </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- Items Tab -->
|
||||
<transition name="fade-customize">
|
||||
<div v-if="activeTab === 'ITEMS'" class="item-tab">
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<!-- <h3 class="page-title">
|
||||
{{ $t('settings.customization.items.title') }}
|
||||
</h3> -->
|
||||
</div>
|
||||
<div class="col-md-4 d-flex flex-row-reverse">
|
||||
<base-button
|
||||
outline
|
||||
class="add-new-tax"
|
||||
color="theme"
|
||||
@click="addItemUnit"
|
||||
>
|
||||
{{ $t('settings.customization.items.add_item_unit') }}
|
||||
</base-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table-component
|
||||
ref="itemTable"
|
||||
:show-filter="false"
|
||||
:data="itemUnits"
|
||||
table-class="table tax-table"
|
||||
class="mb-3"
|
||||
>
|
||||
<table-column
|
||||
:sortable="true"
|
||||
:label="$t('settings.customization.items.units')"
|
||||
show="name"
|
||||
/>
|
||||
<table-column
|
||||
:sortable="false"
|
||||
:filterable="false"
|
||||
cell-class="action-dropdown"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.tax_types.action') }}</span>
|
||||
<v-dropdown>
|
||||
<a slot="activator" href="#">
|
||||
<dot-icon />
|
||||
</a>
|
||||
<v-dropdown-item>
|
||||
<div class="dropdown-item" @click="editItemUnit(row)">
|
||||
<font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon" />
|
||||
{{ $t('general.edit') }}
|
||||
</div>
|
||||
</v-dropdown-item>
|
||||
<v-dropdown-item>
|
||||
<div class="dropdown-item" @click="removeItemUnit(row.id)">
|
||||
<font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" />
|
||||
{{ $t('general.delete') }}
|
||||
</div>
|
||||
</v-dropdown-item>
|
||||
</v-dropdown>
|
||||
</template>
|
||||
</table-column>
|
||||
</table-component>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { validationMixin } from 'vuelidate'
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
const { required, maxLength, alpha } = require('vuelidate/lib/validators')
|
||||
export default {
|
||||
mixins: [validationMixin],
|
||||
data () {
|
||||
return {
|
||||
activeTab: 'INVOICES',
|
||||
invoiceAutogenerate: false,
|
||||
estimateAutogenerate: false,
|
||||
paymentAutogenerate: false,
|
||||
invoices: {
|
||||
invoice_prefix: null,
|
||||
invoice_notes: null,
|
||||
invoice_terms_and_conditions: null
|
||||
},
|
||||
estimates: {
|
||||
estimate_prefix: null,
|
||||
estimate_notes: null,
|
||||
estimate_terms_and_conditions: null
|
||||
},
|
||||
payments: {
|
||||
payment_prefix: null
|
||||
},
|
||||
items: {
|
||||
units: []
|
||||
},
|
||||
currentData: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('item', [
|
||||
'itemUnits'
|
||||
]),
|
||||
...mapGetters('payment', [
|
||||
'paymentModes'
|
||||
])
|
||||
},
|
||||
watch: {
|
||||
activeTab () {
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
invoices: {
|
||||
invoice_prefix: {
|
||||
required,
|
||||
maxLength: maxLength(5),
|
||||
alpha
|
||||
}
|
||||
},
|
||||
estimates: {
|
||||
estimate_prefix: {
|
||||
required,
|
||||
maxLength: maxLength(5),
|
||||
alpha
|
||||
}
|
||||
},
|
||||
payments: {
|
||||
payment_prefix: {
|
||||
required,
|
||||
maxLength: maxLength(5),
|
||||
alpha
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('modal', [
|
||||
'openModal'
|
||||
]),
|
||||
...mapActions('payment', [
|
||||
'deletePaymentMode'
|
||||
]),
|
||||
...mapActions('item', [
|
||||
'deleteItemUnit'
|
||||
]),
|
||||
async setInvoiceSetting () {
|
||||
let data = {
|
||||
key: 'invoice_auto_generate',
|
||||
value: this.invoiceAutogenerate ? 'YES' : 'NO'
|
||||
}
|
||||
let response = await window.axios.put('/api/settings/update-setting', data)
|
||||
if (response.data) {
|
||||
window.toastr['success'](this.$t('general.setting_updated'))
|
||||
}
|
||||
},
|
||||
async setEstimateSetting () {
|
||||
let data = {
|
||||
key: 'estimate_auto_generate',
|
||||
value: this.estimateAutogenerate ? 'YES' : 'NO'
|
||||
}
|
||||
let response = await window.axios.put('/api/settings/update-setting', data)
|
||||
if (response.data) {
|
||||
window.toastr['success'](this.$t('general.setting_updated'))
|
||||
}
|
||||
},
|
||||
async addItemUnit () {
|
||||
this.openModal({
|
||||
'title': this.$t('settings.customization.items.add_item_unit'),
|
||||
'componentName': 'ItemUnit'
|
||||
})
|
||||
this.$refs.itemTable.refresh()
|
||||
},
|
||||
async editItemUnit (data) {
|
||||
this.openModal({
|
||||
'title': this.$t('settings.customization.items.edit_item_unit'),
|
||||
'componentName': 'ItemUnit',
|
||||
'id': data.id,
|
||||
'data': data
|
||||
})
|
||||
this.$refs.itemTable.refresh()
|
||||
},
|
||||
async removeItemUnit (id) {
|
||||
swal({
|
||||
title: this.$t('general.are_you_sure'),
|
||||
text: this.$t('settings.customization.items.item_unit_confirm_delete'),
|
||||
icon: '/assets/icon/trash-solid.svg',
|
||||
buttons: true,
|
||||
dangerMode: true
|
||||
}).then(async (value) => {
|
||||
if (value) {
|
||||
let response = await this.deleteItemUnit(id)
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](this.$t('settings.customization.items.deleted_message'))
|
||||
this.id = null
|
||||
this.$refs.itemTable.refresh()
|
||||
return true
|
||||
}
|
||||
window.toastr['error'](this.$t('settings.customization.items.already_in_use'))
|
||||
}
|
||||
})
|
||||
},
|
||||
async addPaymentMode () {
|
||||
this.openModal({
|
||||
'title': this.$t('settings.customization.payments.add_payment_mode'),
|
||||
'componentName': 'PaymentMode'
|
||||
})
|
||||
this.$refs.table.refresh()
|
||||
},
|
||||
async editPaymentMode (data) {
|
||||
this.openModal({
|
||||
'title': this.$t('settings.customization.payments.edit_payment_mode'),
|
||||
'componentName': 'PaymentMode',
|
||||
'id': data.id,
|
||||
'data': data
|
||||
})
|
||||
this.$refs.table.refresh()
|
||||
},
|
||||
removePaymentMode (id) {
|
||||
swal({
|
||||
title: this.$t('general.are_you_sure'),
|
||||
text: this.$t('settings.customization.payments.payment_mode_confirm_delete'),
|
||||
icon: '/assets/icon/trash-solid.svg',
|
||||
buttons: true,
|
||||
dangerMode: true
|
||||
}).then(async (value) => {
|
||||
if (value) {
|
||||
let response = await this.deletePaymentMode(id)
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](this.$t('settings.customization.payments.deleted_message'))
|
||||
this.id = null
|
||||
this.$refs.table.refresh()
|
||||
return true
|
||||
}
|
||||
window.toastr['error'](this.$t('settings.customization.payments.already_in_use'))
|
||||
}
|
||||
})
|
||||
},
|
||||
changeToUppercase (currentTab) {
|
||||
if (currentTab === 'INVOICES') {
|
||||
this.invoices.invoice_prefix = this.invoices.invoice_prefix.toUpperCase()
|
||||
return true
|
||||
}
|
||||
|
||||
if (currentTab === 'ESTIMATES') {
|
||||
this.estimates.estimate_prefix = this.estimates.estimate_prefix.toUpperCase()
|
||||
return true
|
||||
}
|
||||
|
||||
if (currentTab === 'PAYMENTS') {
|
||||
this.payments.payment_prefix = this.payments.payment_prefix.toUpperCase()
|
||||
return true
|
||||
}
|
||||
},
|
||||
async setPaymentSetting () {
|
||||
let data = {
|
||||
key: 'payment_auto_generate',
|
||||
value: this.paymentAutogenerate ? 'YES' : 'NO'
|
||||
}
|
||||
let response = await window.axios.put('/api/settings/update-setting', data)
|
||||
if (response.data) {
|
||||
window.toastr['success'](this.$t('general.setting_updated'))
|
||||
}
|
||||
},
|
||||
async loadData () {
|
||||
let res = await window.axios.get('/api/settings/get-customize-setting')
|
||||
|
||||
if (res.data) {
|
||||
this.invoices.invoice_prefix = res.data.invoice_prefix
|
||||
this.invoices.invoice_notes = res.data.invoice_notes
|
||||
this.invoices.invoice_terms_and_conditions = res.data.invoice_terms_and_conditions
|
||||
this.estimates.estimate_prefix = res.data.estimate_prefix
|
||||
this.estimates.estimate_notes = res.data.estimate_notes
|
||||
this.estimates.estimate_terms_and_conditions = res.data.estimate_terms_and_conditions
|
||||
this.payments.payment_prefix = res.data.payment_prefix
|
||||
|
||||
if (res.data.invoice_auto_generate === 'YES') {
|
||||
this.invoiceAutogenerate = true
|
||||
} else {
|
||||
this.invoiceAutogenerate = false
|
||||
}
|
||||
|
||||
if (res.data.estimate_auto_generate === 'YES') {
|
||||
this.estimateAutogenerate = true
|
||||
} else {
|
||||
this.estimateAutogenerate = false
|
||||
}
|
||||
|
||||
if (res.data.payment_auto_generate === 'YES') {
|
||||
this.paymentAutogenerate = true
|
||||
} else {
|
||||
this.paymentAutogenerate = false
|
||||
}
|
||||
}
|
||||
},
|
||||
async updateInvoiceSetting () {
|
||||
this.$v.invoices.$touch()
|
||||
|
||||
if (this.$v.invoices.$invalid) {
|
||||
return false
|
||||
}
|
||||
|
||||
let data = {type: 'INVOICES', ...this.invoices}
|
||||
|
||||
if (this.updateSetting(data)) {
|
||||
window.toastr['success'](this.$t('settings.customization.invoices.invoice_setting_updated'))
|
||||
}
|
||||
},
|
||||
async updateEstimateSetting () {
|
||||
this.$v.estimates.$touch()
|
||||
|
||||
if (this.$v.estimates.$invalid) {
|
||||
return false
|
||||
}
|
||||
|
||||
let data = {type: 'ESTIMATES', ...this.estimates}
|
||||
|
||||
if (this.updateSetting(data)) {
|
||||
window.toastr['success'](this.$t('settings.customization.estimates.estimate_setting_updated'))
|
||||
}
|
||||
},
|
||||
async updatePaymentSetting () {
|
||||
this.$v.payments.$touch()
|
||||
|
||||
if (this.$v.payments.$invalid) {
|
||||
return false
|
||||
}
|
||||
|
||||
let data = {type: 'PAYMENTS', ...this.payments}
|
||||
|
||||
if (this.updateSetting(data)) {
|
||||
window.toastr['success'](this.$t('settings.customization.payments.payment_setting_updated'))
|
||||
}
|
||||
},
|
||||
async updateSetting (data) {
|
||||
let res = await window.axios.put('/api/settings/update-customize-setting', data)
|
||||
|
||||
if (res.data.success) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
setActiveTab (val) {
|
||||
this.activeTab = val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.fade-customize-enter-active {
|
||||
transition: opacity 0.9s;
|
||||
}
|
||||
|
||||
.fade-customize-leave-active {
|
||||
transition: opacity 0s;
|
||||
}
|
||||
|
||||
.fade-customize-enter, .fade-customize-leave-to /* .fade-leave-active below version 2.1.8 */ {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
87
resources/assets/js/views/settings/CustomizationSetting.vue
Normal file
87
resources/assets/js/views/settings/CustomizationSetting.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<base-loader v-if="isRequestOnGoing" :show-bg-overlay="true" />
|
||||
<sw-card>
|
||||
<sw-tabs class="p-2">
|
||||
<!-- Invoices -->
|
||||
<sw-tab-item :title="$t('settings.customization.invoices.title')">
|
||||
<invoices-tab :settings="settings" />
|
||||
</sw-tab-item>
|
||||
|
||||
<!-- Estimates -->
|
||||
<sw-tab-item :title="$t('settings.customization.estimates.title')">
|
||||
<estimates-tab :settings="settings" />
|
||||
</sw-tab-item>
|
||||
|
||||
<!-- Payments -->
|
||||
<sw-tab-item :title="$t('settings.customization.payments.title')">
|
||||
<payments-tab :settings="settings" />
|
||||
</sw-tab-item>
|
||||
|
||||
<!-- Items -->
|
||||
<sw-tab-item :title="$t('settings.customization.items.title')">
|
||||
<items-tab />
|
||||
</sw-tab-item>
|
||||
</sw-tabs>
|
||||
</sw-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import InvoicesTab from './customization-tabs/InvoicesTab'
|
||||
import EstimatesTab from './customization-tabs/EstimatesTab'
|
||||
import PaymentsTab from './customization-tabs/PaymentsTab'
|
||||
import ItemsTab from './customization-tabs/ItemsTab'
|
||||
import { mapActions } from 'vuex'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
settings: {},
|
||||
isRequestOnGoing: false,
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
InvoicesTab,
|
||||
EstimatesTab,
|
||||
PaymentsTab,
|
||||
ItemsTab,
|
||||
},
|
||||
|
||||
created() {
|
||||
this.fetchSettings()
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('company', ['fetchCompanySettings']),
|
||||
async fetchSettings() {
|
||||
this.isRequestOnGoing = true
|
||||
let res = await this.fetchCompanySettings([
|
||||
'payment_auto_generate',
|
||||
'payment_prefix',
|
||||
'payment_mail_body',
|
||||
'invoice_auto_generate',
|
||||
'invoice_prefix',
|
||||
'invoice_mail_body',
|
||||
'estimate_auto_generate',
|
||||
'estimate_prefix',
|
||||
'estimate_mail_body',
|
||||
'invoice_billing_address_format',
|
||||
'invoice_shipping_address_format',
|
||||
'invoice_company_address_format',
|
||||
'invoice_mail_body',
|
||||
'payment_mail_body',
|
||||
'payment_company_address_format',
|
||||
'payment_from_customer_address_format',
|
||||
'estimate_company_address_format',
|
||||
'estimate_billing_address_format',
|
||||
'estimate_shipping_address_format',
|
||||
])
|
||||
|
||||
this.settings = res.data
|
||||
this.isRequestOnGoing = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,141 +0,0 @@
|
||||
<template>
|
||||
|
||||
<div class="setting-main-container">
|
||||
<div class="card setting-card">
|
||||
<div class="page-header d-flex justify-content-between">
|
||||
<div>
|
||||
<h3 class="page-title">{{ $t('settings.expense_category.title') }}</h3>
|
||||
<p class="page-sub-title">
|
||||
{{ $t('settings.expense_category.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<base-button
|
||||
outline
|
||||
class="add-new-tax"
|
||||
color="theme"
|
||||
@click="openCategoryModal"
|
||||
>
|
||||
{{ $t('settings.expense_category.add_new_category') }}
|
||||
</base-button>
|
||||
</div>
|
||||
|
||||
<table-component
|
||||
ref="table"
|
||||
:show-filter="false"
|
||||
:data="categories"
|
||||
table-class="table expense-category"
|
||||
>
|
||||
<table-column
|
||||
:label="$t('settings.expense_category.category_name')"
|
||||
show="name"
|
||||
/>
|
||||
<table-column
|
||||
:sortable="true"
|
||||
:filterable="true"
|
||||
:label="$t('settings.expense_category.category_description')"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.expense_category.category_description') }}</span>
|
||||
<div class="notes">
|
||||
<div class="note">{{ row.description }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</table-column>
|
||||
<table-column
|
||||
:sortable="false"
|
||||
:filterable="false"
|
||||
cell-class="action-dropdown"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.expense_category.action') }}</span>
|
||||
<v-dropdown>
|
||||
<a slot="activator" href="#">
|
||||
<dot-icon />
|
||||
</a>
|
||||
<v-dropdown-item>
|
||||
<div class="dropdown-item" @click="EditCategory(row.id)">
|
||||
<font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon" />
|
||||
{{ $t('general.edit') }}
|
||||
</div>
|
||||
</v-dropdown-item>
|
||||
<v-dropdown-item>
|
||||
<div class="dropdown-item" @click="removeExpenseCategory(row.id)">
|
||||
<font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" />
|
||||
{{ $t('general.delete') }}
|
||||
</div>
|
||||
</v-dropdown-item>
|
||||
</v-dropdown>
|
||||
</template>
|
||||
</table-column>
|
||||
</table-component>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
id: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('category', [
|
||||
'categories',
|
||||
'getCategoryById'
|
||||
])
|
||||
},
|
||||
mounted () {
|
||||
this.fetchCategories()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('modal', [
|
||||
'openModal'
|
||||
]),
|
||||
...mapActions('category', [
|
||||
'fetchCategories',
|
||||
'fetchCategory',
|
||||
'deleteCategory'
|
||||
]),
|
||||
async removeExpenseCategory (id, index) {
|
||||
swal({
|
||||
title: this.$t('general.are_you_sure'),
|
||||
text: this.$t('settings.expense_category.confirm_delete'),
|
||||
icon: '/assets/icon/trash-solid.svg',
|
||||
buttons: true,
|
||||
dangerMode: true
|
||||
}).then(async (willDelete) => {
|
||||
if (willDelete) {
|
||||
let response = await this.deleteCategory(id)
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](this.$tc('settings.expense_category.deleted_message'))
|
||||
this.id = null
|
||||
this.$refs.table.refresh()
|
||||
return true
|
||||
} window.toastr['error'](this.$t('settings.expense_category.already_in_use'))
|
||||
}
|
||||
})
|
||||
},
|
||||
openCategoryModal () {
|
||||
this.openModal({
|
||||
'title': this.$t('settings.expense_category.add_category'),
|
||||
'componentName': 'CategoryModal'
|
||||
})
|
||||
this.$refs.table.refresh()
|
||||
},
|
||||
async EditCategory (id) {
|
||||
let response = await this.fetchCategory(id)
|
||||
this.openModal({
|
||||
'title': this.$t('settings.expense_category.edit_category'),
|
||||
'componentName': 'CategoryModal',
|
||||
'id': id,
|
||||
'data': response.data.category
|
||||
})
|
||||
this.$refs.table.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
179
resources/assets/js/views/settings/ExpenseCategorySetting.vue
Normal file
179
resources/assets/js/views/settings/ExpenseCategorySetting.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<sw-card variant="setting-card">
|
||||
<div slot="header" class="flex flex-wrap justify-between lg:flex-no-wrap">
|
||||
<div>
|
||||
<h6 class="sw-section-title">
|
||||
{{ $t('settings.expense_category.title') }}
|
||||
</h6>
|
||||
<p
|
||||
class="mt-2 text-sm leading-snug text-gray-500"
|
||||
style="max-width: 680px"
|
||||
>
|
||||
{{ $t('settings.expense_category.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 lg:mt-0 lg:ml-2">
|
||||
<sw-button
|
||||
variant="primary-outline"
|
||||
size="lg"
|
||||
@click="addExpenseCategory"
|
||||
>
|
||||
<plus-icon class="w-6 h-6 mr-1 -ml-2" />
|
||||
{{ $t('settings.expense_category.add_new_category') }}
|
||||
</sw-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<sw-table-component
|
||||
ref="table"
|
||||
:show-filter="false"
|
||||
:data="fetchData"
|
||||
variant="gray"
|
||||
>
|
||||
<sw-table-column
|
||||
:label="$t('settings.expense_category.category_name')"
|
||||
show="name"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.expense_category.category_name') }}}</span>
|
||||
<span class="mt-6">{{ row.name }}</span>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
|
||||
<sw-table-column
|
||||
:sortable="true"
|
||||
:filterable="true"
|
||||
:label="$t('settings.expense_category.category_description')"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{
|
||||
$t('settings.expense_category.category_description')
|
||||
}}</span>
|
||||
<div class="w-48 overflow-hidden notes">
|
||||
<div
|
||||
class="overflow-hidden whitespace-no-wrap"
|
||||
style="text-overflow: ellipsis"
|
||||
>
|
||||
{{ row.description }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
|
||||
<sw-table-column
|
||||
:sortable="false"
|
||||
:filterable="false"
|
||||
cell-class="action-dropdown"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.expense_category.action') }}</span>
|
||||
<sw-dropdown>
|
||||
<dot-icon slot="activator" class="h-5" />
|
||||
|
||||
<sw-dropdown-item @click="editExpenseCategory(row.id)">
|
||||
<pencil-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('general.edit') }}
|
||||
</sw-dropdown-item>
|
||||
|
||||
<sw-dropdown-item @click="removeExpenseCategory(row.id)">
|
||||
<trash-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('general.delete') }}
|
||||
</sw-dropdown-item>
|
||||
</sw-dropdown>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
</sw-table-component>
|
||||
</sw-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import { TrashIcon, PencilIcon, PlusIcon } from '@vue-hero-icons/solid'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TrashIcon,
|
||||
PencilIcon,
|
||||
PlusIcon,
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters('category', ['categories', 'getCategoryById']),
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('modal', ['openModal']),
|
||||
|
||||
...mapActions('category', [
|
||||
'fetchCategories',
|
||||
'fetchCategory',
|
||||
'deleteCategory',
|
||||
]),
|
||||
|
||||
async fetchData({ page, filter, sort }) {
|
||||
let data = {
|
||||
orderByField: sort.fieldName || 'created_at',
|
||||
orderBy: sort.order || 'desc',
|
||||
page,
|
||||
}
|
||||
|
||||
this.isRequestOngoing = true
|
||||
let response = await this.fetchCategories(data)
|
||||
this.isRequestOngoing = false
|
||||
|
||||
return {
|
||||
data: response.data.categories.data,
|
||||
pagination: {
|
||||
totalPages: response.data.categories.last_page,
|
||||
currentPage: page,
|
||||
count: response.data.categories.count,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
async removeExpenseCategory(id, index) {
|
||||
swal({
|
||||
title: this.$t('general.are_you_sure'),
|
||||
text: this.$t('settings.expense_category.confirm_delete'),
|
||||
icon: '/assets/icon/trash-solid.svg',
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
}).then(async (willDelete) => {
|
||||
if (willDelete) {
|
||||
let response = await this.deleteCategory(id)
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](
|
||||
this.$tc('settings.expense_category.deleted_message')
|
||||
)
|
||||
this.id = null
|
||||
this.$refs.table.refresh()
|
||||
return true
|
||||
}
|
||||
window.toastr['error'](
|
||||
this.$t('settings.expense_category.already_in_use')
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
addExpenseCategory() {
|
||||
this.openModal({
|
||||
title: this.$t('settings.expense_category.add_category'),
|
||||
componentName: 'CategoryModal',
|
||||
refreshData: this.$refs.table.refresh,
|
||||
})
|
||||
},
|
||||
|
||||
async editExpenseCategory(id) {
|
||||
let response = await this.fetchCategory(id)
|
||||
this.openModal({
|
||||
title: this.$t('settings.expense_category.edit_category'),
|
||||
componentName: 'CategoryModal',
|
||||
id: id,
|
||||
data: response.data.category,
|
||||
refreshData: this.$refs.table.refresh,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
297
resources/assets/js/views/settings/FileDiskSetting.vue
Normal file
297
resources/assets/js/views/settings/FileDiskSetting.vue
Normal file
@@ -0,0 +1,297 @@
|
||||
<template>
|
||||
<div class="setting-main-container backup">
|
||||
<sw-card variant="setting-card">
|
||||
<div slot="header" class="flex flex-wrap justify-between lg:flex-no-wrap">
|
||||
<div>
|
||||
<h6 class="sw-section-title">
|
||||
{{ $tc('settings.disk.title', 1) }}
|
||||
</h6>
|
||||
<p
|
||||
class="mt-2 text-sm leading-snug text-gray-500"
|
||||
style="max-width: 680px"
|
||||
>
|
||||
{{ $t('settings.disk.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 lg:mt-0 lg:ml-2">
|
||||
<sw-button
|
||||
variant="primary-outline"
|
||||
size="lg"
|
||||
@click="openCreateDiskModal"
|
||||
>
|
||||
<plus-icon class="w-6 h-6 mr-1 -ml-2" />
|
||||
{{ $t('settings.disk.new_disk') }}
|
||||
</sw-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<sw-table-component
|
||||
ref="table"
|
||||
variant="gray"
|
||||
:show-filter="false"
|
||||
:data="fetchData"
|
||||
table-class="table tax-table"
|
||||
class="mt-0 mb-3"
|
||||
>
|
||||
<sw-table-column :label="$t('settings.disk.disk_name')" show="name">
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.disk.disk_name') }}</span>
|
||||
<span class="mt-6">{{ row.name }}</span>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
<sw-table-column
|
||||
:label="$t('settings.disk.filesystem_driver')"
|
||||
show="driver"
|
||||
/>
|
||||
<sw-table-column :label="$t('settings.disk.disk_type')" show="type" />
|
||||
<sw-table-column
|
||||
:sortable="false"
|
||||
:filterable="false"
|
||||
:label="$t('settings.disk.is_default')"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.disk.is_default') }}</span>
|
||||
<sw-badge
|
||||
:bg-color="
|
||||
$utils.getBadgeStatusColor(row.set_as_default ? 'YES' : 'NO')
|
||||
.bgColor
|
||||
"
|
||||
:color="
|
||||
$utils.getBadgeStatusColor(row.set_as_default ? 'YES' : 'NO')
|
||||
.color
|
||||
"
|
||||
>
|
||||
{{ row.set_as_default ? 'Yes' : 'No'.replace('_', ' ') }}
|
||||
</sw-badge>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
|
||||
<sw-table-column
|
||||
:sortable="false"
|
||||
:filterable="false"
|
||||
cell-class="action-dropdown no-click"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.disk.action') }}</span>
|
||||
<sw-dropdown v-if="isShowAction(row)">
|
||||
<a slot="activator" href="#">
|
||||
<dot-icon />
|
||||
</a>
|
||||
|
||||
<sw-dropdown-item
|
||||
v-if="!row.set_as_default"
|
||||
@click="setDefaultDiskData(row.id)"
|
||||
>
|
||||
<check-circle-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('settings.disk.set_default_disk') }}
|
||||
</sw-dropdown-item>
|
||||
|
||||
<sw-dropdown-item
|
||||
v-if="row.type !== 'SYSTEM'"
|
||||
@click="openEditDiskModal(row)"
|
||||
>
|
||||
<pencil-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('general.edit') }}
|
||||
</sw-dropdown-item>
|
||||
|
||||
<sw-dropdown-item
|
||||
v-if="row.type !== 'SYSTEM' && !row.set_as_default"
|
||||
@click="removeDisk(row.id)"
|
||||
>
|
||||
<trash-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('general.delete') }}
|
||||
</sw-dropdown-item>
|
||||
</sw-dropdown>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
</sw-table-component>
|
||||
|
||||
<sw-divider class="mt-6 mb-4" />
|
||||
|
||||
<h3 class="mb-5 text-lg font-medium text-black">
|
||||
{{ $t('settings.disk.disk_settings') }}
|
||||
</h3>
|
||||
|
||||
<div class="flex">
|
||||
<div class="relative w-12">
|
||||
<sw-switch
|
||||
v-model="save_pdf_to_disk"
|
||||
class="absolute"
|
||||
style="top: -18px"
|
||||
@change="setDiskSettings"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ml-4">
|
||||
<p class="p-0 mb-1 text-base leading-snug text-black">
|
||||
{{ $t('settings.disk.save_pdf_to_disk') }}
|
||||
</p>
|
||||
|
||||
<p class="max-w-lg p-0 m-0 text-xs leading-tight text-gray-500">
|
||||
{{ $t('settings.disk.disk_setting_description') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</sw-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
PlusIcon,
|
||||
TrashIcon,
|
||||
PencilIcon,
|
||||
} from '@vue-hero-icons/solid'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CheckCircleIcon,
|
||||
PlusIcon,
|
||||
TrashIcon,
|
||||
PencilIcon,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
disk: 'local',
|
||||
save_pdf_to_disk: true,
|
||||
loading: false,
|
||||
disks: [],
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getDiskSetting()
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('modal', ['openModal']),
|
||||
|
||||
...mapActions('disks', ['fetchDisks', 'updateDisk', 'deleteFileDisk']),
|
||||
|
||||
...mapActions('company', ['updateCompanySettings', 'fetchCompanySettings']),
|
||||
|
||||
async fetchData({ page, filter, sort }) {
|
||||
let data = {
|
||||
orderByField: sort.fieldName || 'created_at',
|
||||
orderBy: sort.order || 'desc',
|
||||
page,
|
||||
}
|
||||
|
||||
let response = await this.fetchDisks(data)
|
||||
|
||||
return {
|
||||
data: response.data.disks.data,
|
||||
pagination: {
|
||||
totalPages: response.data.disks.last_page,
|
||||
currentPage: page,
|
||||
count: response.data.disks.count,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
isShowAction(disk) {
|
||||
if (!disk.set_as_default) return true
|
||||
|
||||
if (disk.type == 'SYSTEM' && disk.set_as_default) return false
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
openCreateDiskModal() {
|
||||
this.openModal({
|
||||
title: this.$t('settings.disk.new_disk'),
|
||||
componentName: 'FileDiskModal',
|
||||
variant: 'lg',
|
||||
refreshData: this.refreshTable,
|
||||
})
|
||||
},
|
||||
|
||||
openEditDiskModal(data) {
|
||||
this.openModal({
|
||||
title: this.$t('settings.disk.edit_file_disk'),
|
||||
componentName: 'FileDiskModal',
|
||||
variant: 'lg',
|
||||
id: data.id,
|
||||
data,
|
||||
refreshData: this.refreshTable,
|
||||
})
|
||||
},
|
||||
|
||||
refreshTable() {
|
||||
this.$refs.table.refresh()
|
||||
},
|
||||
|
||||
async getDiskSetting(val) {
|
||||
let response = await this.fetchCompanySettings(['save_pdf_to_disk'])
|
||||
|
||||
if (response.data) {
|
||||
this.save_pdf_to_disk =
|
||||
response.data.save_pdf_to_disk === 'YES' ? true : false
|
||||
}
|
||||
},
|
||||
|
||||
async setDiskSettings() {
|
||||
let data = {
|
||||
settings: {
|
||||
save_pdf_to_disk: this.save_pdf_to_disk ? 'YES' : 'NO',
|
||||
},
|
||||
}
|
||||
|
||||
let response = await this.updateCompanySettings(data)
|
||||
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](this.$t('general.setting_updated'))
|
||||
}
|
||||
this.$refs.table.refresh()
|
||||
},
|
||||
|
||||
async setDefaultDiskData(id) {
|
||||
swal({
|
||||
title: this.$t('general.are_you_sure'),
|
||||
text: this.$t('settings.disk.set_default_disk_confirm'),
|
||||
icon: '/assets/icon/check-circle-solid.svg',
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
}).then(async (value) => {
|
||||
if (value) {
|
||||
this.loading = true
|
||||
let data = {
|
||||
set_as_default: true,
|
||||
id,
|
||||
}
|
||||
let response = await this.updateDisk(data)
|
||||
|
||||
if (response.data.success) {
|
||||
this.refreshTable()
|
||||
window.toastr['success'](
|
||||
this.$t('settings.disk.success_set_default_disk')
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async removeDisk(id) {
|
||||
swal({
|
||||
title: this.$t('general.are_you_sure'),
|
||||
text: this.$t('settings.disk.confirm_delete'),
|
||||
icon: '/assets/icon/trash-solid.svg',
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
}).then(async (value) => {
|
||||
if (value) {
|
||||
let response = await this.deleteFileDisk(id)
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](this.$t('settings.disk.deleted_message'))
|
||||
this.refreshTable()
|
||||
return true
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,102 +0,0 @@
|
||||
<template>
|
||||
<div class="main-content">
|
||||
<div class="card setting-card">
|
||||
<div class="page-header">
|
||||
<h3 class="page-title">{{ $t('settings.title') }}</h3>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><router-link slot="item-title" to="/admin/dashboard">{{ $t('general.home') }}</router-link></li>
|
||||
<li class="breadcrumb-item"><router-link slot="item-title" to="#">{{ $t('settings.general') }}</router-link></li>
|
||||
</ol>
|
||||
</div>
|
||||
<form action="" @submit.prevent="submitData">
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="caption">
|
||||
<h6>{{ $t('settings.general') }}</h6>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<base-button icon="backward" color="theme" size="small" type="submit">
|
||||
{{ $t('general.save') }}
|
||||
</base-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group row">
|
||||
<label class="col-md-2 form-control-label">{{ $t('settings.language') }}: </label>
|
||||
<div class="col-md-10">
|
||||
<setting-dropdown
|
||||
:options="languages"
|
||||
:get-data="settings"
|
||||
:current-data="settings.language"
|
||||
type="languages"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-md-2 form-control-label">{{ $t('settings.primary_currency') }}: </label>
|
||||
<div class="col-md-10">
|
||||
<setting-dropdown
|
||||
:options="currencies"
|
||||
:get-data="settings"
|
||||
:current-data="settings.currency"
|
||||
type="currencies"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-md-2 form-control-label">{{ $t('settings.timezone') }}: </label>
|
||||
<div class="col-md-10">
|
||||
<setting-dropdown
|
||||
:options="time_zones"
|
||||
:get-data="settings"
|
||||
:current-data="settings.time_zone"
|
||||
type="time_zones"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-body">
|
||||
<div class="form-group row">
|
||||
<label class="col-md-2 form-control-label">{{ $t('settings.date_format') }}: </label>
|
||||
<div class="col-md-10">
|
||||
<setting-dropdown
|
||||
:options="date_formats"
|
||||
:get-data="settings"
|
||||
:current-data="settings.date_format"
|
||||
type="date_formats"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SettingDropdown from '../components/SettingListBox.vue'
|
||||
import { mapActions } from 'vuex'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
'setting-dropdown': SettingDropdown
|
||||
},
|
||||
data () {
|
||||
return this.$store.state.general
|
||||
},
|
||||
mounted () {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('general', [
|
||||
'loadData',
|
||||
'submitData'
|
||||
])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,107 +0,0 @@
|
||||
<template>
|
||||
<div class="setting-main-container">
|
||||
<div class="card setting-card">
|
||||
<div class="page-header">
|
||||
<h3 class="page-title">{{ $t('settings.mail.mail_config') }}</h3>
|
||||
<p class="page-sub-title">
|
||||
{{ $t('settings.mail.mail_config_desc') }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="mailConfigData">
|
||||
<component
|
||||
:is="mail_driver"
|
||||
:config-data="mailConfigData"
|
||||
:loading="loading"
|
||||
:mail-drivers="mail_drivers"
|
||||
@on-change-driver="(val) => mail_driver = mailConfigData.mail_driver = val"
|
||||
@submit-data="saveEmailConfig"
|
||||
>
|
||||
<base-button
|
||||
:loading="loading"
|
||||
outline
|
||||
class="pull-right mt-4 ml-2"
|
||||
icon="check"
|
||||
color="theme"
|
||||
type="button"
|
||||
@click="openMailTestModal"
|
||||
>
|
||||
{{ $t('general.test_mail_conf') }}
|
||||
</base-button>
|
||||
</component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import MultiSelect from 'vue-multiselect'
|
||||
import { validationMixin } from 'vuelidate'
|
||||
import Smtp from './mailDriver/Smtp'
|
||||
import Mailgun from './mailDriver/Mailgun'
|
||||
import Ses from './mailDriver/Ses'
|
||||
import Basic from './mailDriver/Basic'
|
||||
import { mapActions } from 'vuex'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MultiSelect,
|
||||
Smtp,
|
||||
Mailgun,
|
||||
Ses,
|
||||
sendmail: Basic,
|
||||
mail: Basic
|
||||
},
|
||||
mixins: [validationMixin],
|
||||
data () {
|
||||
return {
|
||||
mailConfigData: null,
|
||||
mail_driver: 'smtp',
|
||||
loading: false,
|
||||
mail_drivers: []
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('modal', [
|
||||
'openModal'
|
||||
]),
|
||||
async loadData () {
|
||||
this.loading = true
|
||||
|
||||
let mailDrivers = await window.axios.get('/api/settings/environment/mail')
|
||||
let mailData = await window.axios.get('/api/settings/environment/mail-env')
|
||||
|
||||
if (mailDrivers.data) {
|
||||
this.mail_drivers = mailDrivers.data
|
||||
}
|
||||
if (mailData.data) {
|
||||
this.mailConfigData = mailData.data
|
||||
this.mail_driver = mailData.data.mail_driver
|
||||
}
|
||||
this.loading = false
|
||||
},
|
||||
async saveEmailConfig (mailConfigData) {
|
||||
this.loading = true
|
||||
try {
|
||||
let response = await window.axios.post('/api/settings/environment/mail', mailConfigData)
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](this.$t('wizard.success.' + response.data.success))
|
||||
} else {
|
||||
window.toastr['error'](this.$t('wizard.errors.' + response.data.error))
|
||||
}
|
||||
this.loading = false
|
||||
return true
|
||||
} catch (e) {
|
||||
window.toastr['error']('Something went wrong')
|
||||
}
|
||||
},
|
||||
openMailTestModal () {
|
||||
this.openModal({
|
||||
'title': 'Test Mail Configuration',
|
||||
'componentName': 'MailTestModal'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
125
resources/assets/js/views/settings/MailConfigSetting.vue
Normal file
125
resources/assets/js/views/settings/MailConfigSetting.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<base-loader v-if="isRequestOnGoing" :show-bg-overlay="true" />
|
||||
<sw-card variant="setting-card">
|
||||
<template slot="header">
|
||||
<h6 class="sw-section-title">
|
||||
{{ $t('settings.mail.mail_config') }}
|
||||
</h6>
|
||||
<p
|
||||
class="mt-2 text-sm leading-snug text-gray-500"
|
||||
style="max-width: 680px"
|
||||
>
|
||||
{{ $t('settings.mail.mail_config_desc') }}
|
||||
</p>
|
||||
</template>
|
||||
<div v-if="mailConfigData">
|
||||
<component
|
||||
:is="mail_driver"
|
||||
:config-data="mailConfigData"
|
||||
:loading="isLoading"
|
||||
:mail-drivers="mail_drivers"
|
||||
@on-change-driver="
|
||||
(val) => (mail_driver = mailConfigData.mail_driver = val)
|
||||
"
|
||||
@submit-data="saveEmailConfig"
|
||||
>
|
||||
<sw-button
|
||||
variant="primary-outline"
|
||||
type="button"
|
||||
class="ml-2"
|
||||
@click="openMailTestModal"
|
||||
>
|
||||
{{ $t('general.test_mail_conf') }}
|
||||
</sw-button>
|
||||
</component>
|
||||
</div>
|
||||
</sw-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Smtp from './mail-driver/SmtpMailDriver'
|
||||
import Mailgun from './mail-driver/MailgunMailDriver'
|
||||
import Ses from './mail-driver/SesMailDriver'
|
||||
import Basic from './mail-driver/BasicMailDriver'
|
||||
import { mapActions } from 'vuex'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Smtp,
|
||||
Mailgun,
|
||||
Ses,
|
||||
sendmail: Basic,
|
||||
mail: Basic,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
mailConfigData: null,
|
||||
mail_driver: 'smtp',
|
||||
isLoading: false,
|
||||
isRequestOnGoing: false,
|
||||
mail_drivers: [],
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('modal', ['openModal']),
|
||||
|
||||
...mapActions('company', [
|
||||
'fetchMailDrivers',
|
||||
'fetchMailConfig',
|
||||
'updateMailConfig',
|
||||
]),
|
||||
|
||||
async loadData() {
|
||||
this.isRequestOnGoing = true
|
||||
let mailDrivers = await this.fetchMailDrivers()
|
||||
|
||||
let mailData = await this.fetchMailConfig()
|
||||
|
||||
if (mailDrivers.data) {
|
||||
this.mail_drivers = mailDrivers.data
|
||||
}
|
||||
|
||||
if (mailData.data) {
|
||||
this.mailConfigData = mailData.data
|
||||
this.mail_driver = mailData.data.mail_driver
|
||||
}
|
||||
this.isRequestOnGoing = false
|
||||
},
|
||||
|
||||
async saveEmailConfig(mailConfigData) {
|
||||
try {
|
||||
this.isLoading = true
|
||||
let response = await this.updateMailConfig(mailConfigData)
|
||||
if (response.data.success) {
|
||||
this.isLoading = false
|
||||
window.toastr['success'](
|
||||
this.$t('wizard.success.' + response.data.success)
|
||||
)
|
||||
} else {
|
||||
window.toastr['error'](
|
||||
this.$t('wizard.errors.' + response.data.error)
|
||||
)
|
||||
}
|
||||
return true
|
||||
} catch (e) {
|
||||
window.toastr['error']('Something went wrong')
|
||||
}
|
||||
},
|
||||
|
||||
openMailTestModal() {
|
||||
this.openModal({
|
||||
title: 'Test Mail Configuration',
|
||||
componentName: 'MailTestModal',
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
160
resources/assets/js/views/settings/NotesSetting.vue
Normal file
160
resources/assets/js/views/settings/NotesSetting.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<sw-card variant="setting-card">
|
||||
<div slot="header" class="flex flex-wrap justify-between lg:flex-no-wrap">
|
||||
<div>
|
||||
<h6 class="sw-section-title">
|
||||
{{ $t('settings.customization.notes.title') }}
|
||||
</h6>
|
||||
<p
|
||||
class="mt-2 text-sm leading-snug text-gray-500"
|
||||
style="max-width: 680px"
|
||||
>
|
||||
{{ $t('settings.customization.notes.description') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 lg:mt-0 lg:ml-2">
|
||||
<sw-button
|
||||
size="lg"
|
||||
variant="primary-outline"
|
||||
@click="openNoteSelectModal"
|
||||
>
|
||||
<plus-icon class="w-6 h-6 mr-1 -ml-2" />
|
||||
{{ $t('settings.customization.notes.add_note') }}
|
||||
</sw-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<sw-table-component
|
||||
ref="table"
|
||||
variant="gray"
|
||||
:show-filter="false"
|
||||
:data="fetchData"
|
||||
>
|
||||
<sw-table-column
|
||||
:label="$t('settings.customization.notes.name')"
|
||||
show="name"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.customization.notes.name') }}</span>
|
||||
<span class="mt-6">{{ row.name }}</span>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
<sw-table-column
|
||||
:label="$t('settings.customization.notes.type')"
|
||||
show="type"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.customization.notes.type') }}</span>
|
||||
<span class="mt-6">{{ row.type }}</span>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
<sw-table-column
|
||||
:sortable="false"
|
||||
:filterable="false"
|
||||
cell-class="action-dropdown"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.tax_types.action') }}</span>
|
||||
|
||||
<sw-dropdown>
|
||||
<dot-icon slot="activator" class="h-5" />
|
||||
|
||||
<sw-dropdown-item @click="editNote(row)">
|
||||
<pencil-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('general.edit') }}
|
||||
</sw-dropdown-item>
|
||||
|
||||
<sw-dropdown-item @click="removeNote(row.id)">
|
||||
<trash-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('general.delete') }}
|
||||
</sw-dropdown-item>
|
||||
</sw-dropdown>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
</sw-table-component>
|
||||
</sw-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
const { required, maxLength, alpha } = require('vuelidate/lib/validators')
|
||||
import { TrashIcon, PencilIcon, PlusIcon } from '@vue-hero-icons/solid'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TrashIcon,
|
||||
PencilIcon,
|
||||
PlusIcon,
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('modal', ['openModal']),
|
||||
|
||||
...mapActions('notes', ['fetchNotes', 'deleteNote']),
|
||||
|
||||
async fetchData({ page, filter, sort }) {
|
||||
let data = {
|
||||
orderByField: sort.fieldName || 'created_at',
|
||||
orderBy: sort.order || 'desc',
|
||||
page,
|
||||
}
|
||||
|
||||
let response = await this.fetchNotes(data)
|
||||
|
||||
return {
|
||||
data: response.data.notes.data,
|
||||
pagination: {
|
||||
totalPages: response.data.notes.last_page,
|
||||
currentPage: page,
|
||||
count: response.data.notes.count,
|
||||
},
|
||||
}
|
||||
},
|
||||
removeNote(id) {
|
||||
swal({
|
||||
title: this.$t('general.are_you_sure'),
|
||||
text: this.$t('settings.customization.notes.note_confirm_delete'),
|
||||
icon: '/assets/icon/trash-solid.svg',
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
}).then(async (value) => {
|
||||
if (value) {
|
||||
let response = await this.deleteNote(id)
|
||||
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](
|
||||
this.$t('settings.customization.notes.deleted_message')
|
||||
)
|
||||
this.$refs.table.refresh()
|
||||
return true
|
||||
}
|
||||
window.toastr['error'](
|
||||
this.$t('settings.customization.notes.already_in_use')
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
editNote(data) {
|
||||
this.openModal({
|
||||
title: this.$t('settings.customization.notes.edit_note'),
|
||||
componentName: 'NoteSelectModal',
|
||||
id: data.id,
|
||||
data: data,
|
||||
variant: 'lg',
|
||||
refreshData: this.$refs.table.refresh,
|
||||
})
|
||||
},
|
||||
|
||||
openNoteSelectModal() {
|
||||
this.openModal({
|
||||
title: this.$t('settings.customization.notes.add_note'),
|
||||
componentName: 'NoteSelectModal',
|
||||
variant: 'lg',
|
||||
refreshData: this.$refs.table.refresh,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,153 +0,0 @@
|
||||
<template>
|
||||
<div class="setting-main-container">
|
||||
<div class="card setting-card">
|
||||
<div class="page-header">
|
||||
<h3 class="page-title">{{ $t('settings.notification.title') }}</h3>
|
||||
<p class="page-sub-title">
|
||||
{{ $t('settings.notification.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<form action="" @submit.prevent="saveEmail()">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('settings.notification.email') }}</label><span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.notification_email.$error"
|
||||
v-model.trim="notification_email"
|
||||
:placeholder="$tc('settings.notification.please_enter_email')"
|
||||
type="text"
|
||||
name="notification_email"
|
||||
icon="envelope"
|
||||
input-class="col-md-6"
|
||||
@input="$v.notification_email.$touch()"
|
||||
/>
|
||||
<div v-if="$v.notification_email.$error">
|
||||
<span v-if="!$v.notification_email.required" class="text-danger">{{ $tc('validation.required') }}</span>
|
||||
<span v-if="!$v.notification_email.email" class="text-danger"> {{ $tc('validation.email_incorrect') }} </span>
|
||||
</div>
|
||||
<base-button
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
class="mt-4"
|
||||
icon="save"
|
||||
color="theme"
|
||||
type="submit"
|
||||
> {{ $tc('settings.notification.save') }} </base-button>
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
<div class="flex-box mt-3 mb-4">
|
||||
<div class="left">
|
||||
<base-switch v-model="notify_invoice_viewed" class="btn-switch" @change="setInvoiceViewd"/>
|
||||
</div>
|
||||
<div class="right ml-15">
|
||||
<p class="box-title"> {{ $t('settings.notification.invoice_viewed') }} </p>
|
||||
<p class="box-desc"> {{ $t('settings.notification.invoice_viewed_desc') }} </p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-box mb-2">
|
||||
<div class="left">
|
||||
<base-switch v-model="notify_estimate_viewed" class="btn-switch" @change="setEstimateViewd"/>
|
||||
</div>
|
||||
<div class="right ml-15">
|
||||
<p class="box-title"> {{ $t('settings.notification.estimate_viewed') }} </p>
|
||||
<p class="box-desc"> {{ $t('settings.notification.estimate_viewed_desc') }} </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { validationMixin } from 'vuelidate'
|
||||
const { required, email } = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
|
||||
mixins: [validationMixin],
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
notification_email: null,
|
||||
notify_invoice_viewed: null,
|
||||
notify_estimate_viewed: null
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
notification_email: {
|
||||
required,
|
||||
email
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
async fetchData () {
|
||||
let response1 = await axios.get('/api/settings/get-setting?key=notify_invoice_viewed')
|
||||
if (response1.data) {
|
||||
let data = response1.data
|
||||
data.notify_invoice_viewed === 'YES' ?
|
||||
this.notify_invoice_viewed = true :
|
||||
this.notify_invoice_viewed = null
|
||||
}
|
||||
let response2 = await axios.get('/api/settings/get-setting?key=notify_estimate_viewed')
|
||||
if (response2.data) {
|
||||
let data = response2.data
|
||||
data.notify_estimate_viewed === 'YES' ?
|
||||
this.notify_estimate_viewed = true :
|
||||
this.notify_estimate_viewed = null
|
||||
}
|
||||
let response3 = await axios.get('/api/settings/get-setting?key=notification_email')
|
||||
if (response3.data) {
|
||||
this.notification_email = response3.data.notification_email
|
||||
}
|
||||
},
|
||||
async saveEmail () {
|
||||
this.$v.$touch()
|
||||
if (this.$v.$invalid) {
|
||||
return true
|
||||
}
|
||||
this.isLoading = true
|
||||
let data = {
|
||||
key: 'notification_email',
|
||||
value: this.notification_email
|
||||
}
|
||||
let response = await axios.put('/api/settings/update-setting', data)
|
||||
if (response.data.success) {
|
||||
this.isLoading = false
|
||||
window.toastr['success'](this.$tc('settings.notification.email_save_message'))
|
||||
}
|
||||
},
|
||||
async setInvoiceViewd (val) {
|
||||
this.$v.$touch()
|
||||
if (this.$v.$invalid) {
|
||||
this.notify_invoice_viewed = !this.notify_invoice_viewed
|
||||
return true
|
||||
}
|
||||
let data = {
|
||||
key: 'notify_invoice_viewed',
|
||||
value: this.notify_invoice_viewed ? 'YES' : 'NO'
|
||||
}
|
||||
|
||||
let response = await axios.put('/api/settings/update-setting', data)
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](this.$tc('general.setting_updated'))
|
||||
}
|
||||
},
|
||||
async setEstimateViewd (val) {
|
||||
this.$v.$touch()
|
||||
if (this.$v.$invalid) {
|
||||
this.notify_estimate_viewed = !this.notify_estimate_viewed
|
||||
return true
|
||||
}
|
||||
let data = {
|
||||
key: 'notify_estimate_viewed',
|
||||
value: this.notify_estimate_viewed ? 'YES' : 'NO'
|
||||
}
|
||||
let response = await axios.put('/api/settings/update-setting', data)
|
||||
if (response.data) {
|
||||
window.toastr['success'](this.$tc('general.setting_updated'))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
228
resources/assets/js/views/settings/NotificationsSetting.vue
Normal file
228
resources/assets/js/views/settings/NotificationsSetting.vue
Normal file
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<base-loader v-if="isRequestOnGoing" :show-bg-overlay="true" />
|
||||
<sw-card variant="setting-card">
|
||||
<template slot="header">
|
||||
<h6 class="sw-section-title">
|
||||
{{ $t('settings.notification.title') }}
|
||||
</h6>
|
||||
<p
|
||||
class="mt-2 text-sm leading-snug text-gray-500"
|
||||
style="max-width: 680px"
|
||||
>
|
||||
{{ $t('settings.notification.description') }}
|
||||
</p>
|
||||
</template>
|
||||
<form action="" @submit.prevent="saveEmail()">
|
||||
<div class="grid-cols-2 col-span-1">
|
||||
<sw-input-group
|
||||
:label="$t('settings.notification.email')"
|
||||
:error="notificationEmailError"
|
||||
class="my-2"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.notification_email.$error"
|
||||
v-model.trim="notification_email"
|
||||
:placeholder="$tc('settings.notification.please_enter_email')"
|
||||
type="text"
|
||||
name="notification_email"
|
||||
icon="envelope"
|
||||
@input="$v.notification_email.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-button
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
class="my-6"
|
||||
>
|
||||
<save-icon v-if="!isLoading" class="mr-2 -ml-1" />
|
||||
{{ $tc('settings.notification.save') }}
|
||||
</sw-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<sw-divider class="mt-1 mb-6" />
|
||||
|
||||
<div class="flex mt-3 mb-4">
|
||||
<div class="relative w-12">
|
||||
<sw-switch
|
||||
v-model="notify_invoice_viewed"
|
||||
class="absolute"
|
||||
style="top: -20px"
|
||||
@change="setInvoiceViewd"
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="p-0 mb-1 text-base leading-snug text-black box-title">
|
||||
{{ $t('settings.notification.invoice_viewed') }}
|
||||
</p>
|
||||
<p
|
||||
class="p-0 m-0 text-xs leading-tight text-gray-500"
|
||||
style="max-width: 480px"
|
||||
>
|
||||
{{ $t('settings.notification.invoice_viewed_desc') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-2">
|
||||
<div class="relative w-12">
|
||||
<sw-switch
|
||||
v-model="notify_estimate_viewed"
|
||||
class="absolute"
|
||||
style="top: -20px"
|
||||
@change="setEstimateViewd"
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="p-0 mb-1 text-base leading-snug text-black box-title">
|
||||
{{ $t('settings.notification.estimate_viewed') }}
|
||||
</p>
|
||||
<p
|
||||
class="p-0 m-0 text-xs leading-tight text-gray-500"
|
||||
style="max-width: 480px"
|
||||
>
|
||||
{{ $t('settings.notification.estimate_viewed_desc') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</sw-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex'
|
||||
const { required, email } = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
notification_email: null,
|
||||
notify_invoice_viewed: null,
|
||||
notify_estimate_viewed: null,
|
||||
isRequestOnGoing: false,
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
notification_email: {
|
||||
required,
|
||||
email,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
notificationEmailError() {
|
||||
if (!this.$v.notification_email.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.notification_email.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
|
||||
if (!this.$v.notification_email.email) {
|
||||
return this.$tc('validation.email_incorrect')
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('company', ['fetchCompanySettings', 'updateCompanySettings']),
|
||||
|
||||
async fetchData() {
|
||||
this.isRequestOnGoing = true
|
||||
let response = await this.fetchCompanySettings([
|
||||
'notify_invoice_viewed',
|
||||
'notify_estimate_viewed',
|
||||
'notification_email',
|
||||
])
|
||||
|
||||
if (response.data) {
|
||||
this.notification_email = response.data.notification_email
|
||||
|
||||
response.data.notify_invoice_viewed === 'YES'
|
||||
? (this.notify_invoice_viewed = true)
|
||||
: (this.notify_invoice_viewed = false)
|
||||
|
||||
response.data.notify_estimate_viewed === 'YES'
|
||||
? (this.notify_estimate_viewed = true)
|
||||
: (this.notify_estimate_viewed = false)
|
||||
}
|
||||
this.isRequestOnGoing = false
|
||||
},
|
||||
|
||||
async saveEmail() {
|
||||
this.$v.$touch()
|
||||
|
||||
if (this.$v.$invalid) {
|
||||
return true
|
||||
}
|
||||
|
||||
this.isLoading = true
|
||||
|
||||
let data = {
|
||||
settings: {
|
||||
notification_email: this.notification_email,
|
||||
},
|
||||
}
|
||||
|
||||
let response = await this.updateCompanySettings(data)
|
||||
|
||||
if (response.data.success) {
|
||||
this.isLoading = false
|
||||
|
||||
window.toastr['success'](
|
||||
this.$tc('settings.notification.email_save_message')
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
async setInvoiceViewd(val) {
|
||||
this.$v.$touch()
|
||||
|
||||
if (this.$v.$invalid) {
|
||||
this.notify_invoice_viewed = !this.notify_invoice_viewed
|
||||
return true
|
||||
}
|
||||
|
||||
let data = {
|
||||
settings: {
|
||||
notify_invoice_viewed: this.notify_invoice_viewed ? 'YES' : 'NO',
|
||||
},
|
||||
}
|
||||
|
||||
let response = await this.updateCompanySettings(data)
|
||||
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](this.$tc('general.setting_updated'))
|
||||
}
|
||||
},
|
||||
|
||||
async setEstimateViewd(val) {
|
||||
this.$v.$touch()
|
||||
|
||||
if (this.$v.$invalid) {
|
||||
this.notify_estimate_viewed = !this.notify_estimate_viewed
|
||||
return true
|
||||
}
|
||||
|
||||
let data = {
|
||||
settings: {
|
||||
notify_estimate_viewed: this.notify_estimate_viewed ? 'YES' : 'NO',
|
||||
},
|
||||
}
|
||||
|
||||
let response = await this.updateCompanySettings(data)
|
||||
|
||||
if (response.data) {
|
||||
window.toastr['success'](this.$tc('general.setting_updated'))
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
148
resources/assets/js/views/settings/PaymentsModeSetting.vue
Normal file
148
resources/assets/js/views/settings/PaymentsModeSetting.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<sw-card variant="setting-card">
|
||||
<div slot="header" class="flex flex-wrap justify-between lg:flex-no-wrap">
|
||||
<div>
|
||||
<h6 class="sw-section-title">
|
||||
{{ $t('settings.customization.payments.payment_modes') }}
|
||||
</h6>
|
||||
<p
|
||||
class="mt-2 text-sm leading-snug text-gray-500"
|
||||
style="max-width: 680px"
|
||||
>
|
||||
{{ $t('settings.customization.payments.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 lg:mt-0 lg:ml-2">
|
||||
<sw-button variant="primary-outline" size="lg" @click="addPaymentMode">
|
||||
<plus-icon class="w-6 h-6 mr-1 -ml-2" />
|
||||
{{ $t('settings.customization.payments.add_payment_mode') }}
|
||||
</sw-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<sw-table-component
|
||||
ref="table"
|
||||
variant="gray"
|
||||
:show-filter="false"
|
||||
:data="fetchData"
|
||||
>
|
||||
<sw-table-column
|
||||
:label="$t('settings.customization.payments.mode_name')"
|
||||
show="name"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.customization.payments.mode_name') }}</span>
|
||||
<span class="mt-6"> {{ row.name }}</span>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
<sw-table-column
|
||||
:sortable="false"
|
||||
:filterable="false"
|
||||
cell-class="action-dropdown"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.tax_types.action') }}</span>
|
||||
|
||||
<sw-dropdown>
|
||||
<dot-icon slot="activator" class="h-5" />
|
||||
|
||||
<sw-dropdown-item @click="editPaymentMode(row)">
|
||||
<pencil-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('general.edit') }}
|
||||
</sw-dropdown-item>
|
||||
|
||||
<sw-dropdown-item @click="removePaymentMode(row.id)">
|
||||
<trash-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('general.delete') }}
|
||||
</sw-dropdown-item>
|
||||
</sw-dropdown>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
</sw-table-component>
|
||||
</sw-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
const { required, maxLength, alpha } = require('vuelidate/lib/validators')
|
||||
import { TrashIcon, PencilIcon, PlusIcon } from '@vue-hero-icons/solid'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TrashIcon,
|
||||
PencilIcon,
|
||||
PlusIcon,
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('modal', ['openModal']),
|
||||
|
||||
...mapActions('payment', ['deletePaymentMode', 'fetchPaymentModes']),
|
||||
|
||||
async fetchData({ page, filter, sort }) {
|
||||
let data = {
|
||||
orderByField: sort.fieldName || 'created_at',
|
||||
orderBy: sort.order || 'desc',
|
||||
page,
|
||||
}
|
||||
|
||||
let response = await this.fetchPaymentModes(data)
|
||||
|
||||
return {
|
||||
data: response.data.paymentMethods.data,
|
||||
pagination: {
|
||||
totalPages: response.data.paymentMethods.last_page,
|
||||
currentPage: page,
|
||||
count: response.data.paymentMethods.count,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
addPaymentMode() {
|
||||
this.openModal({
|
||||
title: this.$t('settings.customization.payments.add_payment_mode'),
|
||||
componentName: 'PaymentMode',
|
||||
refreshData: this.$refs.table.refresh,
|
||||
})
|
||||
},
|
||||
|
||||
editPaymentMode(data) {
|
||||
this.openModal({
|
||||
title: this.$t('settings.customization.payments.edit_payment_mode'),
|
||||
componentName: 'PaymentMode',
|
||||
id: data.id,
|
||||
data: data,
|
||||
refreshData: this.$refs.table.refresh,
|
||||
})
|
||||
},
|
||||
|
||||
removePaymentMode(id) {
|
||||
swal({
|
||||
title: this.$t('general.are_you_sure'),
|
||||
text: this.$t(
|
||||
'settings.customization.payments.payment_mode_confirm_delete'
|
||||
),
|
||||
icon: '/assets/icon/trash-solid.svg',
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
}).then(async (value) => {
|
||||
if (value) {
|
||||
let response = await this.deletePaymentMode(id)
|
||||
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](
|
||||
this.$t('settings.customization.payments.deleted_message')
|
||||
)
|
||||
this.id = null
|
||||
this.$refs.table.refresh()
|
||||
return true
|
||||
}
|
||||
window.toastr['error'](
|
||||
this.$t('settings.customization.payments.already_in_use')
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,251 +0,0 @@
|
||||
<template>
|
||||
<div class="setting-main-container">
|
||||
<div class="card setting-card">
|
||||
<div class="page-header">
|
||||
<h3 class="page-title">{{ $tc('settings.preferences.preference',2) }}</h3>
|
||||
<p class="page-sub-title">
|
||||
{{ $t('settings.preferences.general_settings') }}
|
||||
</p>
|
||||
</div>
|
||||
<form action="" @submit.prevent="updatePreferencesData">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4 form-group">
|
||||
<label class="input-label">{{ $tc('settings.preferences.currency') }}</label><span class="text-danger"> * </span>
|
||||
<base-select
|
||||
v-model="formData.currency"
|
||||
:options="currencies"
|
||||
:custom-label="currencyNameWithCode"
|
||||
:class="{'error': $v.formData.currency.$error }"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:allow-empty="false"
|
||||
:placeholder="$tc('settings.currencies.select_currency')"
|
||||
label="name"
|
||||
track-by="id"
|
||||
/>
|
||||
<div v-if="$v.formData.currency.$error">
|
||||
<span v-if="!$v.formData.currency.required" class="text-danger">{{ $tc('validation.required') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4 form-group">
|
||||
<label class="input-label">{{ $tc('settings.preferences.language') }}</label><span class="text-danger"> * </span>
|
||||
<base-select
|
||||
v-model="formData.language"
|
||||
:options="languages"
|
||||
:class="{'error': $v.formData.language.$error }"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:allow-empty="false"
|
||||
:placeholder="$tc('settings.preferences.select_language')"
|
||||
label="name"
|
||||
track-by="code"
|
||||
/>
|
||||
<div v-if="$v.formData.language.$error">
|
||||
<span v-if="!$v.formData.language.required" class="text-danger">{{ $tc('validation.required') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4 form-group">
|
||||
<label class="input-label">{{ $tc('settings.preferences.time_zone') }}</label><span class="text-danger"> * </span>
|
||||
<base-select
|
||||
v-model="formData.timeZone"
|
||||
:options="timeZones"
|
||||
:class="{'error': $v.formData.timeZone.$error }"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:allow-empty="false"
|
||||
:placeholder="$tc('settings.preferences.select_time_zone')"
|
||||
label="key"
|
||||
track-by="key"
|
||||
/>
|
||||
<div v-if="$v.formData.timeZone.$error">
|
||||
<span v-if="!$v.formData.timeZone.required" class="text-danger">{{ $tc('validation.required') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4 form-group">
|
||||
<label class="input-label">{{ $tc('settings.preferences.date_format') }}</label><span class="text-danger"> * </span>
|
||||
<base-select
|
||||
v-model="formData.dateFormat"
|
||||
:options="dateFormats"
|
||||
:class="{'error': $v.formData.dateFormat.$error }"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:allow-empty="false"
|
||||
:placeholder="$tc('settings.preferences.select_date_formate')"
|
||||
label="display_date"
|
||||
/>
|
||||
<div v-if="$v.formData.dateFormat.$error">
|
||||
<span v-if="!$v.formData.dateFormat.required" class="text-danger">{{ $tc('validation.required') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4 form-group">
|
||||
<label class="input-label">{{ $tc('settings.preferences.fiscal_year') }}</label><span class="text-danger"> * </span>
|
||||
<base-select
|
||||
v-model="formData.fiscalYear"
|
||||
:options="fiscalYears"
|
||||
:class="{'error': $v.formData.fiscalYear.$error }"
|
||||
:show-labels="false"
|
||||
:allow-empty="false"
|
||||
:searchable="true"
|
||||
:placeholder="$tc('settings.preferences.select_financial_year')"
|
||||
label="key"
|
||||
track-by="value"
|
||||
/>
|
||||
<div v-if="$v.formData.fiscalYear.$error">
|
||||
<span v-if="!$v.formData.fiscalYear.required" class="text-danger">{{ $tc('settings.company_info.errors.required') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12 input-group">
|
||||
<base-button
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
icon="save"
|
||||
color="theme"
|
||||
type="submit"
|
||||
>
|
||||
{{ $tc('settings.company_info.save') }}
|
||||
</base-button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
<div class="page-header mt-3">
|
||||
<h3 class="page-title">{{ $t('settings.preferences.discount_setting') }}</h3>
|
||||
<div class="flex-box">
|
||||
<div class="left">
|
||||
<base-switch
|
||||
v-model="discount_per_item"
|
||||
class="btn-switch"
|
||||
@change="setDiscount"
|
||||
/>
|
||||
</div>
|
||||
<div class="right ml-15">
|
||||
<p class="box-title"> {{ $t('settings.preferences.discount_per_item') }} </p>
|
||||
<p class="box-desc"> {{ $t('settings.preferences.discount_setting_description') }} </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import MultiSelect from 'vue-multiselect'
|
||||
import { validationMixin } from 'vuelidate'
|
||||
import { mapActions } from 'vuex'
|
||||
const { required } = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
components: { MultiSelect },
|
||||
mixins: [validationMixin],
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
formData: {
|
||||
language: null,
|
||||
currency: null,
|
||||
timeZone: null,
|
||||
dateFormat: null,
|
||||
fiscalYear: null
|
||||
},
|
||||
discount_per_item: null,
|
||||
languages: [],
|
||||
currencies: [],
|
||||
timeZones: [],
|
||||
dateFormats: [],
|
||||
fiscalYears: []
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
formData: {
|
||||
currency: {
|
||||
required
|
||||
},
|
||||
language: {
|
||||
required
|
||||
},
|
||||
dateFormat: {
|
||||
required
|
||||
},
|
||||
timeZone: {
|
||||
required
|
||||
},
|
||||
fiscalYear: {
|
||||
required
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.setInitialData()
|
||||
this.getDiscountSettings()
|
||||
},
|
||||
methods: {
|
||||
currencyNameWithCode ({name, code}) {
|
||||
return `${code} - ${name}`
|
||||
},
|
||||
...mapActions('currency', [
|
||||
'setDefaultCurrency'
|
||||
]),
|
||||
...mapActions('preferences', [
|
||||
'loadData',
|
||||
'editPreferences'
|
||||
]),
|
||||
async setInitialData () {
|
||||
let response = await this.loadData()
|
||||
this.languages = [...response.data.languages]
|
||||
this.currencies = response.data.currencies
|
||||
this.dateFormats = response.data.date_formats
|
||||
this.timeZones = response.data.time_zones
|
||||
this.fiscalYears = [...response.data.fiscal_years]
|
||||
this.formData.currency = response.data.currencies.find(currency => currency.id == response.data.selectedCurrency)
|
||||
this.formData.language = response.data.languages.find(language => language.code == response.data.selectedLanguage)
|
||||
this.formData.timeZone = response.data.time_zones.find(timeZone => timeZone.value == response.data.time_zone)
|
||||
this.formData.fiscalYear = response.data.fiscal_years.find(fiscalYear => fiscalYear.value == response.data.fiscal_year)
|
||||
this.formData.dateFormat = response.data.date_formats.find(dateFormat => dateFormat.carbon_format_value == response.data.carbon_date_format)
|
||||
},
|
||||
async updatePreferencesData () {
|
||||
this.$v.formData.$touch()
|
||||
if (this.$v.$invalid) {
|
||||
return true
|
||||
}
|
||||
this.isLoading = true
|
||||
let data = {
|
||||
currency: this.formData.currency.id,
|
||||
time_zone: this.formData.timeZone.value,
|
||||
fiscal_year: this.formData.fiscalYear.value,
|
||||
language: this.formData.language.code,
|
||||
carbon_date_format: this.formData.dateFormat.carbon_format_value,
|
||||
moment_date_format: this.formData.dateFormat.moment_format_value
|
||||
}
|
||||
let response = await this.editPreferences(data)
|
||||
if (response.data.success) {
|
||||
this.isLoading = false
|
||||
window.i18n.locale = this.formData.language.code
|
||||
this.setDefaultCurrency(this.formData.currency)
|
||||
window.toastr['success'](this.$t('settings.preferences.updated_message'))
|
||||
return true
|
||||
}
|
||||
window.toastr['error'](response.data.error)
|
||||
return true
|
||||
},
|
||||
async getDiscountSettings () {
|
||||
let response = await axios.get('/api/settings/get-setting?key=discount_per_item')
|
||||
if (response.data) {
|
||||
response.data.discount_per_item === 'YES' ?
|
||||
this.discount_per_item = true :
|
||||
this.discount_per_item = false
|
||||
}
|
||||
},
|
||||
async setDiscount () {
|
||||
let data = {
|
||||
key: 'discount_per_item',
|
||||
value: this.discount_per_item ? 'YES' : 'NO'
|
||||
}
|
||||
let response = await axios.put('/api/settings/update-setting', data)
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](this.$t('general.setting_updated'))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
365
resources/assets/js/views/settings/PreferencesSetting.vue
Normal file
365
resources/assets/js/views/settings/PreferencesSetting.vue
Normal file
@@ -0,0 +1,365 @@
|
||||
<template>
|
||||
<form action="" @submit.prevent="updatePreferencesData" class="relative">
|
||||
<base-loader v-if="isRequestOnGoing" :show-bg-overlay="true" />
|
||||
<sw-card variant="setting-card">
|
||||
<template slot="header">
|
||||
<h6 class="sw-section-title">
|
||||
{{ $t('settings.menu_title.preferences') }}
|
||||
</h6>
|
||||
|
||||
<p
|
||||
class="mt-2 text-sm leading-snug text-gray-500"
|
||||
style="max-width: 680px"
|
||||
>
|
||||
{{ $t('settings.preferences.general_settings') }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<div class="grid gap-6 sm:grid-col-1 md:grid-cols-2">
|
||||
<sw-input-group
|
||||
:label="$tc('settings.preferences.currency')"
|
||||
:error="currencyError"
|
||||
required
|
||||
>
|
||||
<sw-select
|
||||
v-model="formData.currency"
|
||||
:options="currencies"
|
||||
:custom-label="currencyNameWithCode"
|
||||
:class="{ error: $v.formData.currency.$error }"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:allow-empty="false"
|
||||
:placeholder="$tc('settings.currencies.select_currency')"
|
||||
class="mt-2"
|
||||
label="name"
|
||||
track-by="id"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$tc('settings.preferences.default_language')"
|
||||
:error="languageError"
|
||||
required
|
||||
>
|
||||
<sw-select
|
||||
v-model="formData.language"
|
||||
:options="languages"
|
||||
:class="{ error: $v.formData.language.$error }"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:allow-empty="false"
|
||||
:placeholder="$tc('settings.preferences.select_language')"
|
||||
class="mt-2"
|
||||
label="name"
|
||||
track-by="code"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$tc('settings.preferences.time_zone')"
|
||||
:error="timeZoneError"
|
||||
required
|
||||
>
|
||||
<sw-select
|
||||
v-model="formData.timeZone"
|
||||
:options="timeZones"
|
||||
:class="{ error: $v.formData.timeZone.$error }"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:allow-empty="false"
|
||||
:placeholder="$tc('settings.preferences.select_time_zone')"
|
||||
class="mt-2"
|
||||
label="key"
|
||||
track-by="key"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$tc('settings.preferences.date_format')"
|
||||
:error="dateFormatError"
|
||||
required
|
||||
>
|
||||
<sw-select
|
||||
v-model="formData.dateFormat"
|
||||
:options="dateFormats"
|
||||
:class="{ error: $v.formData.dateFormat.$error }"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:allow-empty="false"
|
||||
:placeholder="$tc('settings.preferences.select_date_format')"
|
||||
class="mt-2"
|
||||
label="display_date"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$tc('settings.preferences.fiscal_year')"
|
||||
:error="fiscalYearError"
|
||||
class="mb-2"
|
||||
required
|
||||
>
|
||||
<sw-select
|
||||
v-model="formData.fiscalYear"
|
||||
:options="fiscalYears"
|
||||
:class="{ error: $v.formData.fiscalYear.$error }"
|
||||
:show-labels="false"
|
||||
:allow-empty="false"
|
||||
:searchable="true"
|
||||
:placeholder="$tc('settings.preferences.select_financial_year')"
|
||||
label="key"
|
||||
track-by="value"
|
||||
/>
|
||||
</sw-input-group>
|
||||
</div>
|
||||
|
||||
<sw-button
|
||||
class="mt-6"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
>
|
||||
<save-icon v-if="!isLoading" class="mr-2 -ml-1" />
|
||||
{{ $tc('settings.company_info.save') }}
|
||||
</sw-button>
|
||||
|
||||
<sw-divider class="mt-6 mb-8" />
|
||||
|
||||
<div class="flex">
|
||||
<div class="relative w-12">
|
||||
<sw-switch
|
||||
v-model="discount_per_item"
|
||||
class="absolute"
|
||||
style="top: -18px"
|
||||
@change="setDiscount"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ml-15">
|
||||
<p class="p-0 mb-1 text-base leading-snug text-black">
|
||||
{{ $t('settings.preferences.discount_per_item') }}
|
||||
</p>
|
||||
<p
|
||||
class="p-0 m-0 text-xs leading-tight text-gray-500"
|
||||
style="max-width: 480px"
|
||||
>
|
||||
{{ $t('settings.preferences.discount_setting_description') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</sw-card>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
const { required } = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
formData: {
|
||||
language: null,
|
||||
currency: null,
|
||||
timeZone: null,
|
||||
dateFormat: null,
|
||||
fiscalYear: null,
|
||||
},
|
||||
isRequestOnGoing: false,
|
||||
discount_per_item: null,
|
||||
}
|
||||
},
|
||||
|
||||
validations: {
|
||||
formData: {
|
||||
currency: {
|
||||
required,
|
||||
},
|
||||
language: {
|
||||
required,
|
||||
},
|
||||
dateFormat: {
|
||||
required,
|
||||
},
|
||||
timeZone: {
|
||||
required,
|
||||
},
|
||||
fiscalYear: {
|
||||
required,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'currencies',
|
||||
'timeZones',
|
||||
'dateFormats',
|
||||
'fiscalYears',
|
||||
'languages',
|
||||
]),
|
||||
|
||||
...mapGetters('company', ['defaultFiscalYear', 'defaultTimeZone']),
|
||||
|
||||
currencyError() {
|
||||
if (!this.$v.formData.currency.$error) {
|
||||
return ''
|
||||
}
|
||||
if (!this.$v.formData.currency.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
|
||||
languageError() {
|
||||
if (!this.$v.formData.language.$error) {
|
||||
return ''
|
||||
}
|
||||
if (!this.$v.formData.language.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
|
||||
timeZoneError() {
|
||||
if (!this.$v.formData.timeZone.$error) {
|
||||
return ''
|
||||
}
|
||||
if (!this.$v.formData.timeZone.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
|
||||
fiscalYearError() {
|
||||
if (!this.$v.formData.fiscalYear.$error) {
|
||||
return ''
|
||||
}
|
||||
if (!this.$v.formData.fiscalYear.required) {
|
||||
return this.$tc('settings.company_info.errors.required')
|
||||
}
|
||||
},
|
||||
|
||||
dateFormatError() {
|
||||
if (!this.$v.formData.dateFormat.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.formData.dateFormat.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
this.setInitialData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('company', [
|
||||
'setDefaultCurrency',
|
||||
'fetchCompanySettings',
|
||||
'updateCompanySettings',
|
||||
]),
|
||||
|
||||
...mapActions([
|
||||
'fetchLanguages',
|
||||
'fetchFiscalYears',
|
||||
'fetchDateFormats',
|
||||
'fetchTimeZones',
|
||||
]),
|
||||
|
||||
currencyNameWithCode({ name, code }) {
|
||||
return `${code} - ${name}`
|
||||
},
|
||||
|
||||
async setInitialData() {
|
||||
this.isRequestOnGoing = true
|
||||
|
||||
await this.fetchDateFormats()
|
||||
await this.fetchLanguages()
|
||||
await this.fetchFiscalYears()
|
||||
await this.fetchTimeZones()
|
||||
|
||||
let response = await this.fetchCompanySettings([
|
||||
'currency',
|
||||
'time_zone',
|
||||
'language',
|
||||
'fiscal_year',
|
||||
'carbon_date_format',
|
||||
'moment_date_format',
|
||||
'discount_per_item',
|
||||
])
|
||||
|
||||
if (response.data) {
|
||||
response.data.discount_per_item === 'YES'
|
||||
? (this.discount_per_item = true)
|
||||
: (this.discount_per_item = false)
|
||||
|
||||
this.formData.currency = this.currencies.find(
|
||||
(currency) => currency.id == response.data.currency
|
||||
)
|
||||
|
||||
this.formData.language = this.languages.find(
|
||||
(language) => language.code == response.data.language
|
||||
)
|
||||
|
||||
this.formData.timeZone = this.timeZones.find(
|
||||
(timeZone) => timeZone.value == this.defaultTimeZone
|
||||
)
|
||||
|
||||
this.formData.fiscalYear = this.fiscalYears.find(
|
||||
(fiscalYear) => fiscalYear.value == this.defaultFiscalYear
|
||||
)
|
||||
|
||||
this.formData.dateFormat = this.dateFormats.find(
|
||||
(dateFormat) =>
|
||||
dateFormat.carbon_format_value == response.data.carbon_date_format
|
||||
)
|
||||
}
|
||||
|
||||
this.isRequestOnGoing = false
|
||||
},
|
||||
|
||||
async updatePreferencesData() {
|
||||
this.$v.formData.$touch()
|
||||
if (this.$v.$invalid) {
|
||||
return true
|
||||
}
|
||||
this.isLoading = true
|
||||
let data = {
|
||||
settings: {
|
||||
currency: this.formData.currency.id,
|
||||
time_zone: this.formData.timeZone.value,
|
||||
fiscal_year: this.formData.fiscalYear.value,
|
||||
language: this.formData.language.code,
|
||||
carbon_date_format: this.formData.dateFormat.carbon_format_value,
|
||||
moment_date_format: this.formData.dateFormat.moment_format_value,
|
||||
},
|
||||
}
|
||||
let response = await this.updateCompanySettings(data)
|
||||
if (response.data.success) {
|
||||
this.isLoading = false
|
||||
// window.i18n.locale = this.formData.language.code
|
||||
this.setDefaultCurrency(this.formData.currency)
|
||||
window.toastr['success'](
|
||||
this.$t('settings.preferences.updated_message')
|
||||
)
|
||||
return true
|
||||
}
|
||||
window.toastr['error'](response.data.error)
|
||||
return true
|
||||
},
|
||||
|
||||
async setDiscount() {
|
||||
let data = {
|
||||
settings: {
|
||||
discount_per_item: this.discount_per_item ? 'YES' : 'NO',
|
||||
},
|
||||
}
|
||||
let response = await this.updateCompanySettings(data)
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](this.$t('general.setting_updated'))
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
213
resources/assets/js/views/settings/SettingsIndex.vue
Normal file
213
resources/assets/js/views/settings/SettingsIndex.vue
Normal file
@@ -0,0 +1,213 @@
|
||||
<template>
|
||||
<base-page>
|
||||
<div class="pb-6">
|
||||
<sw-page-header :title="$tc('settings.setting', 1)">
|
||||
<sw-breadcrumb slot="breadcrumbs">
|
||||
<sw-breadcrumb-item
|
||||
:title="$t('general.home')"
|
||||
to="/admin/dashboard"
|
||||
/>
|
||||
<sw-breadcrumb-item
|
||||
:title="$tc('settings.setting', 2)"
|
||||
to="/admin/settings/user-profile"
|
||||
active
|
||||
/>
|
||||
</sw-breadcrumb>
|
||||
</sw-page-header>
|
||||
</div>
|
||||
|
||||
<div class="w-full mb-6 select-wrapper xl:hidden">
|
||||
<sw-select
|
||||
:options="menuItems"
|
||||
v-model="currentSetting"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:allow-empty="false"
|
||||
:custom-label="getCustomLabel"
|
||||
@input="navigateToSetting"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-12">
|
||||
<div class="hidden col-span-3 mt-1 xl:block">
|
||||
<sw-list>
|
||||
<sw-list-item
|
||||
v-for="(menuItem, index) in menuItems"
|
||||
:title="$t(menuItem.title)"
|
||||
:key="index"
|
||||
:to="menuItem.link"
|
||||
:active="hasActiveUrl(menuItem.link)"
|
||||
tag-name="router-link"
|
||||
class="py-3"
|
||||
>
|
||||
<component slot="icon" :is="menuItem.icon" class="h-5" />
|
||||
</sw-list-item>
|
||||
</sw-list>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 xl:col-span-9">
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view />
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</base-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
UserIcon,
|
||||
OfficeBuildingIcon,
|
||||
BellIcon,
|
||||
CheckCircleIcon,
|
||||
ClipboardListIcon,
|
||||
CubeIcon,
|
||||
ClipboardCheckIcon,
|
||||
} from '@vue-hero-icons/outline'
|
||||
|
||||
import {
|
||||
RefreshIcon,
|
||||
CogIcon,
|
||||
MailIcon,
|
||||
PencilAltIcon,
|
||||
CloudUploadIcon,
|
||||
FolderIcon,
|
||||
DatabaseIcon,
|
||||
CreditCardIcon,
|
||||
} from '@vue-hero-icons/solid'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
UserIcon,
|
||||
OfficeBuildingIcon,
|
||||
PencilAltIcon,
|
||||
CogIcon,
|
||||
CheckCircleIcon,
|
||||
ClipboardListIcon,
|
||||
MailIcon,
|
||||
BellIcon,
|
||||
FolderIcon,
|
||||
RefreshIcon,
|
||||
CubeIcon,
|
||||
CloudUploadIcon,
|
||||
DatabaseIcon,
|
||||
CreditCardIcon,
|
||||
ClipboardCheckIcon,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
currentSetting: {
|
||||
link: '/admin/settings/user-profile',
|
||||
title: 'settings.menu_title.account_settings',
|
||||
icon: 'user-icon',
|
||||
},
|
||||
menuItems: [
|
||||
{
|
||||
link: '/admin/settings/user-profile',
|
||||
title: 'settings.menu_title.account_settings',
|
||||
icon: 'user-icon',
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/company-info',
|
||||
title: 'settings.menu_title.company_information',
|
||||
icon: 'office-building-icon',
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/preferences',
|
||||
title: 'settings.menu_title.preferences',
|
||||
icon: 'cog-icon',
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/customization',
|
||||
title: 'settings.menu_title.customization',
|
||||
icon: 'pencil-alt-icon',
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/notifications',
|
||||
title: 'settings.menu_title.notifications',
|
||||
icon: 'bell-icon',
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/tax-types',
|
||||
title: 'settings.menu_title.tax_types',
|
||||
icon: 'check-circle-icon',
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/payment-mode',
|
||||
title: 'settings.menu_title.payment_modes',
|
||||
icon: 'credit-card-icon',
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/custom-fields',
|
||||
title: 'settings.menu_title.custom_fields',
|
||||
icon: 'cube-icon',
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/notes',
|
||||
title: 'settings.menu_title.notes',
|
||||
icon: 'clipboard-check-icon',
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/expense-category',
|
||||
title: 'settings.menu_title.expense_category',
|
||||
icon: 'clipboard-list-icon',
|
||||
},
|
||||
|
||||
{
|
||||
link: '/admin/settings/mail-configuration',
|
||||
title: 'settings.mail.mail_config',
|
||||
icon: 'mail-icon',
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/file-disk',
|
||||
title: 'settings.menu_title.file_disk',
|
||||
icon: 'folder-icon',
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/backup',
|
||||
title: 'settings.menu_title.backup',
|
||||
icon: 'database-icon',
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/update-app',
|
||||
title: 'settings.menu_title.update_app',
|
||||
icon: 'refresh-icon',
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
'$route.path'(newValue) {
|
||||
if (newValue === '/admin/settings') {
|
||||
this.$router.push('/admin/settings/user-profile')
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.currentSetting = this.menuItems.find(
|
||||
(item) => item.link == this.$route.path
|
||||
)
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.$route.path === '/admin/settings') {
|
||||
this.$router.push('/admin/settings/user-profile')
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getCustomLabel({ title }) {
|
||||
return this.$t(title)
|
||||
},
|
||||
hasActiveUrl(url) {
|
||||
return this.$route.path.indexOf(url) > -1
|
||||
},
|
||||
navigateToSetting(setting) {
|
||||
this.$router.push(setting.link)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,195 +0,0 @@
|
||||
<template>
|
||||
<div class="setting-main-container">
|
||||
<div class="card setting-card">
|
||||
<div class="page-header d-flex justify-content-between">
|
||||
<div>
|
||||
<h3 class="page-title">
|
||||
{{ $t('settings.tax_types.title') }}
|
||||
</h3>
|
||||
<p class="page-sub-title">
|
||||
{{ $t('settings.tax_types.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<base-button
|
||||
outline
|
||||
class="add-new-tax"
|
||||
color="theme"
|
||||
@click="openTaxModal"
|
||||
>
|
||||
{{ $t('settings.tax_types.add_new_tax') }}
|
||||
</base-button>
|
||||
</div>
|
||||
|
||||
<table-component
|
||||
ref="table"
|
||||
:show-filter="false"
|
||||
:data="taxTypes"
|
||||
table-class="table tax-table"
|
||||
class="mb-3"
|
||||
>
|
||||
<table-column
|
||||
:sortable="true"
|
||||
:label="$t('settings.tax_types.tax_name')"
|
||||
show="name"
|
||||
/>
|
||||
<table-column
|
||||
:sortable="true"
|
||||
:filterable="true"
|
||||
:label="$t('settings.tax_types.compound_tax')"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.tax_types.compound_tax') }}</span>
|
||||
<div class="compound-tax">
|
||||
{{ row.compound_tax ? 'Yes' : 'No' }}
|
||||
</div>
|
||||
</template>
|
||||
</table-column>
|
||||
<table-column
|
||||
:sortable="true"
|
||||
:filterable="true"
|
||||
:label="$t('settings.tax_types.percent')"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.tax_types.percent') }}</span>
|
||||
{{ row.percent }} %
|
||||
</template>
|
||||
</table-column>
|
||||
<table-column
|
||||
:sortable="false"
|
||||
:filterable="false"
|
||||
cell-class="action-dropdown"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.tax_types.action') }}</span>
|
||||
<v-dropdown>
|
||||
<a slot="activator" href="#">
|
||||
<dot-icon />
|
||||
</a>
|
||||
<v-dropdown-item>
|
||||
<div class="dropdown-item" @click="EditTax(row.id)">
|
||||
<font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon" />
|
||||
{{ $t('general.edit') }}
|
||||
</div>
|
||||
</v-dropdown-item>
|
||||
<v-dropdown-item>
|
||||
<div class="dropdown-item" @click="removeTax(row.id)">
|
||||
<font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" />
|
||||
{{ $t('general.delete') }}
|
||||
</div>
|
||||
</v-dropdown-item>
|
||||
</v-dropdown>
|
||||
</template>
|
||||
</table-column>
|
||||
</table-component>
|
||||
<hr>
|
||||
<div class="page-header mt-3">
|
||||
<h3 class="page-title">
|
||||
{{ $t('settings.tax_types.tax_settings') }}
|
||||
</h3>
|
||||
<div class="flex-box">
|
||||
<div class="left">
|
||||
<base-switch
|
||||
v-model="formData.tax_per_item"
|
||||
class="btn-switch"
|
||||
@change="setTax"
|
||||
/>
|
||||
</div>
|
||||
<div class="right ml-15">
|
||||
<p class="box-title"> {{ $t('settings.tax_types.tax_per_item') }} </p>
|
||||
<p class="box-desc"> {{ $t('settings.tax_types.tax_setting_description') }} </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
id: null,
|
||||
formData: {
|
||||
tax_per_item: false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('taxType', [
|
||||
'taxTypes',
|
||||
'getTaxTypeById'
|
||||
])
|
||||
},
|
||||
mounted () {
|
||||
this.getTaxSetting()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('modal', [
|
||||
'openModal'
|
||||
]),
|
||||
...mapActions('taxType', [
|
||||
'indexLoadData',
|
||||
'deleteTaxType',
|
||||
'fetchTaxType'
|
||||
]),
|
||||
async getTaxSetting (val) {
|
||||
let response = await axios.get('/api/settings/get-setting?key=tax_per_item')
|
||||
|
||||
if (response.data && response.data.tax_per_item === 'YES') {
|
||||
this.formData.tax_per_item = true
|
||||
} else {
|
||||
this.formData.tax_per_item = false
|
||||
}
|
||||
},
|
||||
async setTax (val) {
|
||||
let data = {
|
||||
key: 'tax_per_item',
|
||||
value: this.formData.tax_per_item ? 'YES' : 'NO'
|
||||
}
|
||||
let response = await axios.put('/api/settings/update-setting', data)
|
||||
if (response.data) {
|
||||
window.toastr['success'](this.$t('general.setting_updated'))
|
||||
}
|
||||
},
|
||||
async removeTax (id, index) {
|
||||
swal({
|
||||
title: this.$t('general.are_you_sure'),
|
||||
text: this.$t('settings.tax_types.confirm_delete'),
|
||||
icon: '/assets/icon/trash-solid.svg',
|
||||
buttons: true,
|
||||
dangerMode: true
|
||||
}).then(async (value) => {
|
||||
if (value) {
|
||||
let response = await this.deleteTaxType(id)
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](this.$t('settings.tax_types.deleted_message'))
|
||||
this.id = null
|
||||
this.$refs.table.refresh()
|
||||
return true
|
||||
}
|
||||
window.toastr['error'](this.$t('settings.tax_types.already_in_use'))
|
||||
}
|
||||
})
|
||||
},
|
||||
openTaxModal () {
|
||||
this.openModal({
|
||||
'title': this.$t('settings.tax_types.add_tax'),
|
||||
'componentName': 'TaxTypeModal'
|
||||
})
|
||||
this.$refs.table.refresh()
|
||||
},
|
||||
async EditTax (id) {
|
||||
let response = await this.fetchTaxType(id)
|
||||
this.openModal({
|
||||
'title': this.$t('settings.tax_types.edit_tax'),
|
||||
'componentName': 'TaxTypeModal',
|
||||
'id': id,
|
||||
'data': response.data.taxType
|
||||
})
|
||||
this.$refs.table.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
243
resources/assets/js/views/settings/TaxTypesSetting.vue
Normal file
243
resources/assets/js/views/settings/TaxTypesSetting.vue
Normal file
@@ -0,0 +1,243 @@
|
||||
<template>
|
||||
<sw-card variant="setting-card">
|
||||
<div slot="header" class="flex flex-wrap justify-between lg:flex-no-wrap">
|
||||
<div>
|
||||
<h6 class="sw-section-title">
|
||||
{{ $t('settings.tax_types.title') }}
|
||||
</h6>
|
||||
<p
|
||||
class="mt-2 text-sm leading-snug text-gray-500"
|
||||
style="max-width: 680px"
|
||||
>
|
||||
{{ $t('settings.tax_types.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 lg:mt-0 lg:ml-2">
|
||||
<sw-button size="lg" variant="primary-outline" @click="openTaxModal">
|
||||
<plus-icon class="w-6 h-6 mr-1 -ml-2" />
|
||||
{{ $t('settings.tax_types.add_new_tax') }}
|
||||
</sw-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<sw-table-component
|
||||
ref="table"
|
||||
:show-filter="false"
|
||||
:data="fetchData"
|
||||
table-class="table"
|
||||
variant="gray"
|
||||
>
|
||||
<sw-table-column
|
||||
:sortable="true"
|
||||
:label="$t('settings.tax_types.tax_name')"
|
||||
show="name"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.tax_types.tax_name') }}</span>
|
||||
<span class="mt-6">{{ row.name }}</span>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
|
||||
<sw-table-column
|
||||
:sortable="true"
|
||||
:filterable="true"
|
||||
:label="$t('settings.tax_types.compound_tax')"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.tax_types.compound_tax') }}</span>
|
||||
<sw-badge
|
||||
:bg-color="
|
||||
$utils.getBadgeStatusColor(row.compound_tax ? 'YES' : 'NO')
|
||||
.bgColor
|
||||
"
|
||||
:color="
|
||||
$utils.getBadgeStatusColor(row.compound_tax ? 'YES' : 'NO').color
|
||||
"
|
||||
>
|
||||
{{ row.compound_tax ? 'Yes' : 'No'.replace('_', ' ') }}
|
||||
</sw-badge>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
|
||||
<sw-table-column
|
||||
:sortable="true"
|
||||
:filterable="true"
|
||||
:label="$t('settings.tax_types.percent')"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.tax_types.percent') }}</span>
|
||||
{{ row.percent }} %
|
||||
</template>
|
||||
</sw-table-column>
|
||||
|
||||
<sw-table-column
|
||||
:sortable="false"
|
||||
:filterable="false"
|
||||
cell-class="action-dropdown"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.tax_types.action') }}</span>
|
||||
<sw-dropdown>
|
||||
<dot-icon slot="activator" />
|
||||
|
||||
<sw-dropdown-item @click="editTax(row.id)">
|
||||
<pencil-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('general.edit') }}
|
||||
</sw-dropdown-item>
|
||||
|
||||
<sw-dropdown-item @click="removeTax(row.id)">
|
||||
<trash-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('general.delete') }}
|
||||
</sw-dropdown-item>
|
||||
</sw-dropdown>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
</sw-table-component>
|
||||
|
||||
<sw-divider class="my-8" />
|
||||
|
||||
<div class="flex mt-2">
|
||||
<div class="relative w-12">
|
||||
<sw-switch
|
||||
v-model="formData.tax_per_item"
|
||||
class="absolute"
|
||||
style="top: -20px"
|
||||
@change="setTax"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ml-4">
|
||||
<p class="p-0 mb-1 text-base leading-snug text-black box-title">
|
||||
{{ $t('settings.tax_types.tax_per_item') }}
|
||||
</p>
|
||||
|
||||
<p
|
||||
class="p-0 m-0 text-xs leading-4 text-gray-500"
|
||||
style="max-width: 480px"
|
||||
>
|
||||
{{ $t('settings.tax_types.tax_setting_description') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</sw-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import { TrashIcon, PencilIcon, PlusIcon } from '@vue-hero-icons/solid'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TrashIcon,
|
||||
PencilIcon,
|
||||
PlusIcon,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
tax_per_item: false,
|
||||
},
|
||||
isRequestOnGoing: false,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters('taxType', ['taxTypes', 'getTaxTypeById']),
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getTaxSetting()
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('modal', ['openModal']),
|
||||
...mapActions('taxType', [
|
||||
'indexLoadData',
|
||||
'deleteTaxType',
|
||||
'fetchTaxType',
|
||||
'fetchTaxTypes',
|
||||
]),
|
||||
...mapActions('company', ['fetchCompanySettings', 'updateCompanySettings']),
|
||||
|
||||
async fetchData({ page, filter, sort }) {
|
||||
let data = {
|
||||
orderByField: sort.fieldName || 'created_at',
|
||||
orderBy: sort.order || 'desc',
|
||||
page,
|
||||
}
|
||||
|
||||
let response = await this.fetchTaxTypes(data)
|
||||
|
||||
return {
|
||||
data: response.data.taxTypes.data,
|
||||
pagination: {
|
||||
totalPages: response.data.taxTypes.last_page,
|
||||
currentPage: page,
|
||||
count: response.data.taxTypes.count,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
async getTaxSetting() {
|
||||
let response = await this.fetchCompanySettings(['tax_per_item'])
|
||||
if (response.data) {
|
||||
this.formData.tax_per_item = response.data.tax_per_item === 'YES'
|
||||
}
|
||||
},
|
||||
|
||||
async setTax(val) {
|
||||
let data = {
|
||||
settings: {
|
||||
tax_per_item: this.formData.tax_per_item ? 'YES' : 'NO',
|
||||
},
|
||||
}
|
||||
let response = await this.updateCompanySettings(data)
|
||||
if (response.data) {
|
||||
window.toastr['success'](this.$t('general.setting_updated'))
|
||||
}
|
||||
},
|
||||
|
||||
async removeTax(id, index) {
|
||||
swal({
|
||||
title: this.$t('general.are_you_sure'),
|
||||
text: this.$t('settings.tax_types.confirm_delete'),
|
||||
icon: '/assets/icon/trash-solid.svg',
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
}).then(async (value) => {
|
||||
if (value) {
|
||||
let response = await this.deleteTaxType(id)
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](
|
||||
this.$t('settings.tax_types.deleted_message')
|
||||
)
|
||||
this.$refs.table.refresh()
|
||||
return true
|
||||
}
|
||||
window.toastr['error'](this.$t('settings.tax_types.already_in_use'))
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
openTaxModal() {
|
||||
this.openModal({
|
||||
title: this.$t('settings.tax_types.add_tax'),
|
||||
componentName: 'TaxTypeModal',
|
||||
refreshData: this.$refs.table.refresh,
|
||||
})
|
||||
},
|
||||
|
||||
async editTax(id) {
|
||||
let response = await this.fetchTaxType(id)
|
||||
this.openModal({
|
||||
title: this.$t('settings.tax_types.edit_tax'),
|
||||
componentName: 'TaxTypeModal',
|
||||
id: id,
|
||||
data: response.data.taxType,
|
||||
refreshData: this.$refs.table.refresh,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,108 +1,141 @@
|
||||
<template>
|
||||
<div class="setting-main-container update-container">
|
||||
<div class="card setting-card">
|
||||
<div class="page-header">
|
||||
<h3 class="page-title">{{ $t('settings.update_app.title') }}</h3>
|
||||
<p class="page-sub-title">
|
||||
{{ $t('settings.update_app.description') }}
|
||||
</p>
|
||||
<label class="input-label">{{
|
||||
$t('settings.update_app.current_version')
|
||||
}}</label
|
||||
<sw-card variant="setting-card">
|
||||
<template slot="header">
|
||||
<h6 class="sw-section-title">
|
||||
{{ $t('settings.update_app.title') }}
|
||||
</h6>
|
||||
<p
|
||||
class="mt-2 text-sm leading-snug text-gray-500"
|
||||
style="max-width: 680px"
|
||||
>
|
||||
{{ $t('settings.update_app.description') }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<div class="m-0">
|
||||
<label class="text-sm not-italic font-medium input-label">
|
||||
{{ $t('settings.update_app.current_version') }}
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="box-border flex w-16 p-3 my-2 text-sm text-gray-500 bg-gray-200 border border-gray-200 border-solid rounded-md version"
|
||||
>
|
||||
{{ currentVersion }}
|
||||
</label>
|
||||
|
||||
<sw-button
|
||||
:loading="isCheckingforUpdate"
|
||||
:disabled="isCheckingforUpdate || isUpdating"
|
||||
variant="primary-outline"
|
||||
class="mt-6"
|
||||
@click="checkUpdate"
|
||||
>
|
||||
{{ $t('settings.update_app.check_update') }}
|
||||
</sw-button>
|
||||
|
||||
<sw-divider v-if="isUpdateAvailable" class="mt-2 mb-4" />
|
||||
|
||||
<div v-show="!isUpdating" v-if="isUpdateAvailable" class="mt-4 content">
|
||||
<h6 class="mb-8 sw-section-title">
|
||||
{{ $t('settings.update_app.avail_update') }}
|
||||
</h6>
|
||||
<label class="text-sm not-italic font-medium input-label">
|
||||
{{ $t('settings.update_app.next_version') }} </label
|
||||
><br />
|
||||
<label class="version mb-4">{{ currentVersion }}</label>
|
||||
<base-button
|
||||
:outline="true"
|
||||
:disabled="isCheckingforUpdate || isUpdating"
|
||||
size="large"
|
||||
color="theme"
|
||||
class="mb-4"
|
||||
@click="checkUpdate"
|
||||
<label
|
||||
class="box-border flex w-16 p-3 my-2 text-sm text-gray-500 bg-gray-200 border border-gray-200 border-solid rounded-md version"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:class="{ update: isCheckingforUpdate }"
|
||||
style="margin-right: 10px;"
|
||||
icon="sync-alt"
|
||||
/>
|
||||
{{ $t('settings.update_app.check_update') }}
|
||||
</base-button>
|
||||
<hr />
|
||||
<div v-show="!isUpdating" v-if="isUpdateAvailable" class="mt-4 content">
|
||||
<h3 class="page-title mb-3">
|
||||
{{ $t('settings.update_app.avail_update') }}
|
||||
</h3>
|
||||
<label class="input-label">{{
|
||||
$t('settings.update_app.next_version')
|
||||
}}</label
|
||||
><br />
|
||||
<label class="version">{{ updateData.version }}</label>
|
||||
<p
|
||||
class="page-sub-title"
|
||||
style="white-space: pre-wrap;"
|
||||
v-html="description"
|
||||
>
|
||||
</p>
|
||||
<label class="input-label">
|
||||
{{ $t('settings.update_app.requirements') }}
|
||||
</label>
|
||||
<div
|
||||
{{ updateData.version }}
|
||||
</label>
|
||||
<p
|
||||
class="mb-8 text-sm leading-snug text-gray-500"
|
||||
style="white-space: pre-wrap; max-width: 480px"
|
||||
v-html="description"
|
||||
>
|
||||
</p>
|
||||
<label class="text-sm not-italic font-medium input-label">
|
||||
{{ $t('settings.update_app.requirements') }}
|
||||
</label>
|
||||
<table class="w-1/2 mt-2 border-2 border-gray-200 table-fixed">
|
||||
<tr
|
||||
class="p-2 border-2 border-gray-200"
|
||||
v-for="(ext, i) in requiredExtentions"
|
||||
:key="i"
|
||||
class="col-md-8 p-0"
|
||||
>
|
||||
<div class="update-requirements">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>{{ i }}</span>
|
||||
<span v-if="ext" class="verified" />
|
||||
<span v-else class="not-verified" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<base-button
|
||||
size="large"
|
||||
icon="rocket"
|
||||
color="theme"
|
||||
class="mt-5"
|
||||
v-if="hasUiUpdate"
|
||||
@click="onUpdateApp"
|
||||
>
|
||||
{{ $t('settings.update_app.update') }}
|
||||
</base-button>
|
||||
</div>
|
||||
|
||||
<div v-if="isUpdating" class="mt-4 content">
|
||||
<div class="d-flex flex-row justify-content-between">
|
||||
<div>
|
||||
<h3 class="page-title">
|
||||
{{ $t('settings.update_app.update_progress') }}
|
||||
</h3>
|
||||
<p class="page-sub-title">
|
||||
{{ $t('settings.update_app.progress_text') }}
|
||||
</p>
|
||||
</div>
|
||||
<font-awesome-icon icon="spinner" class="update-spinner fa-spin" />
|
||||
</div>
|
||||
<ul class="update-steps-container">
|
||||
<li class="update-step" v-for="step in updateSteps">
|
||||
<p class="update-step-text">{{ $t(step.translationKey) }}</p>
|
||||
<div class="update-status-container">
|
||||
<span v-if="step.time" class="update-time">
|
||||
{{step.time}}
|
||||
</span>
|
||||
<span :class="'update-status status-' + getStatus(step)">
|
||||
{{getStatus(step)}}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<td width="70%" class="p-2 text-sm truncate">
|
||||
{{ i }}
|
||||
</td>
|
||||
<td width="30%" class="p-2 text-sm text-right">
|
||||
<span
|
||||
v-if="ext"
|
||||
class="inline-block w-4 h-4 ml-3 mr-2 rounded-full bg-success"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="inline-block w-4 h-4 ml-3 mr-2 rounded-full bg-danger"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<sw-button
|
||||
size="lg"
|
||||
class="mt-10"
|
||||
variant="primary"
|
||||
@click="onUpdateApp"
|
||||
>
|
||||
{{ $t('settings.update_app.update') }}
|
||||
</sw-button>
|
||||
</div>
|
||||
|
||||
<div v-if="isUpdating" class="relative flex justify-between mt-4 content">
|
||||
<div>
|
||||
<h6 class="m-0 mb-3 font-medium sw-section-title">
|
||||
{{ $t('settings.update_app.update_progress') }}
|
||||
</h6>
|
||||
<p
|
||||
class="mb-8 text-sm leading-snug text-gray-500"
|
||||
style="max-width: 480px"
|
||||
>
|
||||
{{ $t('settings.update_app.progress_text') }}
|
||||
</p>
|
||||
</div>
|
||||
<loading-icon
|
||||
class="absolute right-0 h-6 m-1 animate-spin text-primary-400"
|
||||
/>
|
||||
</div>
|
||||
<!-- -->
|
||||
<ul v-if="isUpdating" class="w-full p-0 list-none">
|
||||
<li
|
||||
class="flex justify-between w-full py-3 border-b border-gray-200 border-solid last:border-b-0"
|
||||
v-for="step in updateSteps"
|
||||
>
|
||||
<p class="m-0 text-sm leading-8">{{ $t(step.translationKey) }}</p>
|
||||
<div class="flex flex-row items-center">
|
||||
<span v-if="step.time" class="mr-3 text-xs text-gray-500">
|
||||
{{ step.time }}
|
||||
</span>
|
||||
<span
|
||||
class="block py-1 text-sm text-center uppercase rounded-full"
|
||||
:class="statusClass(step)"
|
||||
style="width: 88px"
|
||||
>
|
||||
{{ getStatus(step) }}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</sw-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LoadingIcon from '../../components/icon/LoadingIcon'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LoadingIcon,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isShowProgressBar: false,
|
||||
@@ -113,38 +146,39 @@ export default {
|
||||
interval: null,
|
||||
description: '',
|
||||
currentVersion: '',
|
||||
requiredExtentions: null,
|
||||
updateSteps: [
|
||||
{
|
||||
translationKey: 'settings.update_app.download_zip_file',
|
||||
stepUrl: '/api/update/download',
|
||||
stepUrl: '/api/v1/update/download',
|
||||
time: null,
|
||||
started: false,
|
||||
completed: false,
|
||||
},
|
||||
{
|
||||
translationKey: 'settings.update_app.unzipping_package',
|
||||
stepUrl: '/api/update/unzip',
|
||||
stepUrl: '/api/v1/update/unzip',
|
||||
time: null,
|
||||
started: false,
|
||||
completed: false,
|
||||
},
|
||||
{
|
||||
translationKey: 'settings.update_app.copying_files',
|
||||
stepUrl: '/api/update/copy',
|
||||
stepUrl: '/api/v1/update/copy',
|
||||
time: null,
|
||||
started: false,
|
||||
completed: false,
|
||||
},
|
||||
{
|
||||
translationKey: 'settings.update_app.running_migrations',
|
||||
stepUrl: '/api/update/migrate',
|
||||
stepUrl: '/api/v1/update/migrate',
|
||||
time: null,
|
||||
started: false,
|
||||
completed: false,
|
||||
},
|
||||
{
|
||||
translationKey: 'settings.update_app.finishing_update',
|
||||
stepUrl: '/api/update/finish',
|
||||
stepUrl: '/api/v1/update/finish',
|
||||
time: null,
|
||||
started: false,
|
||||
completed: false,
|
||||
@@ -178,15 +212,14 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
mounted() {
|
||||
window.axios.get('/api/settings/app/version').then((res) => {
|
||||
window.axios.get('/api/v1/app/version').then((res) => {
|
||||
this.currentVersion = res.data.version
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
closeHandler() {
|
||||
console.log('closing')
|
||||
},
|
||||
getStatus(step) {
|
||||
if (step.started && step.completed) {
|
||||
return 'finished'
|
||||
@@ -198,15 +231,34 @@ export default {
|
||||
return 'error'
|
||||
}
|
||||
},
|
||||
|
||||
statusClass(step) {
|
||||
const status = this.getStatus(step)
|
||||
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return 'text-primary-800 bg-gray-200'
|
||||
break
|
||||
case 'finished':
|
||||
return 'text-teal-500 bg-teal-100'
|
||||
break
|
||||
case 'running':
|
||||
return 'text-blue-400 bg-blue-100'
|
||||
break
|
||||
case 'error':
|
||||
return 'text-danger bg-red-200'
|
||||
break
|
||||
}
|
||||
},
|
||||
|
||||
async checkUpdate() {
|
||||
try {
|
||||
this.isCheckingforUpdate = true
|
||||
let response = await window.axios.get('/api/check/update')
|
||||
let response = await window.axios.get('/api/v1/check/update')
|
||||
this.isCheckingforUpdate = false
|
||||
|
||||
if (!response.data.version) {
|
||||
window.toastr['info'](this.$t('settings.update_app.latest_message'))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -214,6 +266,7 @@ export default {
|
||||
this.updateData.isMinor = response.data.is_minor
|
||||
this.updateData.version = response.data.version.version
|
||||
this.description = response.data.version.description
|
||||
this.requiredExtentions = response.data.version.extensions
|
||||
this.isUpdateAvailable = true
|
||||
this.requiredExtentions = response.data.version.extensions
|
||||
this.minPhpVesrion = response.data.version.minimum_php_version
|
||||
@@ -224,6 +277,7 @@ export default {
|
||||
window.toastr['error']('Something went wrong')
|
||||
}
|
||||
},
|
||||
|
||||
async onUpdateApp() {
|
||||
let path = null
|
||||
if (!this.allowToUpdate) {
|
||||
@@ -274,6 +328,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onUpdateFailed(translationKey) {
|
||||
let stepName = this.$t(translationKey)
|
||||
swal({
|
||||
@@ -293,25 +348,3 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.update-requirements {
|
||||
/* display: flex;
|
||||
justify-content: space-between; */
|
||||
padding: 10px;
|
||||
border: 1px solid #eaf1fb;
|
||||
}
|
||||
.update {
|
||||
transform: rotate(360deg);
|
||||
animation: rotating 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotating {
|
||||
0% {
|
||||
transform: rotate(0);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,224 +0,0 @@
|
||||
<template>
|
||||
<div class="setting-main-container">
|
||||
<form action="" @submit.prevent="updateUserData">
|
||||
<div class="card setting-card">
|
||||
<div class="page-header">
|
||||
<h3 class="page-title">{{ $t('settings.account_settings.account_settings') }}</h3>
|
||||
<p class="page-sub-title">
|
||||
{{ $t('settings.account_settings.section_description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="input-label">{{ $tc('settings.account_settings.profile_picture') }}</label>
|
||||
<div id="pick-avatar" class="image-upload-box avatar-upload">
|
||||
<div class="overlay">
|
||||
<font-awesome-icon class="white-icon" icon="camera"/>
|
||||
</div>
|
||||
<img v-if="previewAvatar" :src="previewAvatar" class="preview-logo">
|
||||
<div v-if="!previewAvatar" class="upload-content">
|
||||
<font-awesome-icon class="upload-icon" icon="cloud-upload-alt"/>
|
||||
<p class="upload-text"> {{ $tc('general.choose_file') }} </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<avatar-cropper
|
||||
:labels="{ submit: 'Submit', cancel: 'Cancel'}"
|
||||
:cropper-options="cropperOptions"
|
||||
:output-options="cropperOutputOptions"
|
||||
:output-quality="0.8"
|
||||
:upload-handler="cropperHandler"
|
||||
trigger="#pick-avatar"
|
||||
@changed="setFileObject"
|
||||
@error="handleUploadError"
|
||||
/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4 form-group">
|
||||
<label class="input-label">{{ $tc('settings.account_settings.name') }}</label>
|
||||
<base-input
|
||||
v-model="formData.name"
|
||||
:invalid="$v.formData.name.$error"
|
||||
:placeholder="$t('settings.user_profile.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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4 form-group">
|
||||
<label class="input-label">{{ $tc('settings.account_settings.email') }}</label>
|
||||
<base-input
|
||||
v-model="formData.email"
|
||||
:invalid="$v.formData.email.$error"
|
||||
:placeholder="$t('settings.user_profile.email')"
|
||||
@input="$v.formData.email.$touch()"
|
||||
/>
|
||||
<div v-if="$v.formData.email.$error">
|
||||
<span v-if="!$v.formData.email.required" class="text-danger">{{ $tc('validation.required') }}</span>
|
||||
<span v-if="!$v.formData.email.email" class="text-danger">{{ $tc('validation.email_incorrect') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4 form-group">
|
||||
<label class="input-label">{{ $tc('settings.account_settings.password') }}</label>
|
||||
<base-input
|
||||
v-model="formData.password"
|
||||
:invalid="$v.formData.password.$error"
|
||||
:placeholder="$t('settings.user_profile.password')"
|
||||
type="password"
|
||||
@input="$v.formData.password.$touch()"
|
||||
/>
|
||||
<div v-if="$v.formData.password.$error">
|
||||
<span v-if="!$v.formData.password.minLength" class="text-danger"> {{ $tc('validation.password_min_length', $v.formData.password.$params.minLength.min, {count: $v.formData.password.$params.minLength.min}) }} </span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4 form-group">
|
||||
<label class="input-label">{{ $tc('settings.account_settings.confirm_password') }}</label>
|
||||
<base-input
|
||||
v-model="formData.confirm_password"
|
||||
:invalid="$v.formData.confirm_password.$error"
|
||||
:placeholder="$t('settings.user_profile.confirm_password')"
|
||||
type="password"
|
||||
@input="$v.formData.confirm_password.$touch()"
|
||||
/>
|
||||
<div v-if="$v.formData.confirm_password.$error">
|
||||
<span v-if="!$v.formData.confirm_password.sameAsPassword" class="text-danger">{{ $tc('validation.password_incorrect') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-12 input-group">
|
||||
<base-button
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
icon="save"
|
||||
color="theme"
|
||||
type="submit"
|
||||
>
|
||||
{{ $tc('settings.account_settings.save') }}
|
||||
</base-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { validationMixin } from 'vuelidate'
|
||||
import { mapActions } from 'vuex'
|
||||
import AvatarCropper from 'vue-avatar-cropper'
|
||||
const { required, requiredIf, sameAs, email, minLength } = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
components: { AvatarCropper },
|
||||
mixins: [validationMixin],
|
||||
data () {
|
||||
return {
|
||||
cropperOutputOptions: {
|
||||
width: 150,
|
||||
height: 150
|
||||
},
|
||||
cropperOptions: {
|
||||
autoCropArea: 1,
|
||||
viewMode: 0,
|
||||
movable: true,
|
||||
zoomable: true
|
||||
},
|
||||
formData: {
|
||||
name: null,
|
||||
email: null,
|
||||
password: null,
|
||||
confirm_password: null
|
||||
},
|
||||
isLoading: false,
|
||||
previewAvatar: null,
|
||||
fileObject: null
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
formData: {
|
||||
name: {
|
||||
required
|
||||
},
|
||||
email: {
|
||||
required,
|
||||
email
|
||||
},
|
||||
password: {
|
||||
minLength: minLength(5)
|
||||
},
|
||||
confirm_password: {
|
||||
required: requiredIf('isRequired'),
|
||||
sameAsPassword: sameAs('password')
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isRequired () {
|
||||
if (this.formData.password === null || this.formData.password === undefined || this.formData.password === '') {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.setInitialData()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('userProfile', [
|
||||
'loadData',
|
||||
'editUser',
|
||||
'uploadAvatar'
|
||||
]),
|
||||
cropperHandler (cropper) {
|
||||
this.previewAvatar = cropper.getCroppedCanvas().toDataURL(this.cropperOutputMime)
|
||||
},
|
||||
setFileObject (file) {
|
||||
this.fileObject = file
|
||||
},
|
||||
handleUploadError (message, type, xhr) {
|
||||
window.toastr['error']('Oops! Something went wrong...')
|
||||
},
|
||||
async setInitialData () {
|
||||
let response = await this.loadData()
|
||||
this.formData.name = response.data.name
|
||||
this.formData.email = response.data.email
|
||||
if (response.data.avatar) {
|
||||
this.previewAvatar = response.data.avatar
|
||||
} else {
|
||||
this.previewAvatar = '/images/default-avatar.jpg'
|
||||
}
|
||||
},
|
||||
async updateUserData () {
|
||||
this.$v.formData.$touch()
|
||||
if (this.$v.$invalid) {
|
||||
return true
|
||||
}
|
||||
this.isLoading = true
|
||||
let data = {
|
||||
name: this.formData.name,
|
||||
email: this.formData.email
|
||||
}
|
||||
if (this.formData.password != null && this.formData.password !== undefined && this.formData.password !== '') {
|
||||
data = { ...data, password: this.formData.password }
|
||||
}
|
||||
let response = await this.editUser(data)
|
||||
if (response.data.success) {
|
||||
this.isLoading = false
|
||||
if (this.fileObject && this.previewAvatar) {
|
||||
let avatarData = new FormData()
|
||||
avatarData.append('admin_avatar', JSON.stringify({
|
||||
name: this.fileObject.name,
|
||||
data: this.previewAvatar
|
||||
}))
|
||||
this.uploadAvatar(avatarData)
|
||||
}
|
||||
window.toastr['success'](this.$t('settings.account_settings.updated_message'))
|
||||
return true
|
||||
}
|
||||
window.toastr['error'](response.data.error)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
372
resources/assets/js/views/settings/UserProfileSetting.vue
Normal file
372
resources/assets/js/views/settings/UserProfileSetting.vue
Normal file
@@ -0,0 +1,372 @@
|
||||
<template>
|
||||
<form @submit.prevent="updateUserData" class="relative h-full">
|
||||
<base-loader v-if="isRequestOnGoing" :show-bg-overlay="true" />
|
||||
<sw-card variant="setting-card">
|
||||
<template slot="header">
|
||||
<h6 class="sw-section-title">
|
||||
{{ $t('settings.account_settings.account_settings') }}
|
||||
</h6>
|
||||
<p
|
||||
class="mt-2 text-sm leading-snug text-gray-500"
|
||||
style="max-width: 680px"
|
||||
>
|
||||
{{ $t('settings.account_settings.section_description') }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<div class="grid mb-4 md:grid-cols-6">
|
||||
<div>
|
||||
<label
|
||||
class="text-sm not-italic font-medium leading-4 text-black whitespace-no-wrap"
|
||||
>
|
||||
{{ $tc('settings.account_settings.profile_picture') }}
|
||||
</label>
|
||||
<sw-avatar
|
||||
:preview-avatar="previewAvatar"
|
||||
:label="$tc('general.choose_file')"
|
||||
@changed="onChange"
|
||||
@uploadHandler="onUploadHandler"
|
||||
@handleUploadError="onHandleUploadError"
|
||||
>
|
||||
<template v-slot:icon>
|
||||
<cloud-upload-icon
|
||||
class="h-5 mb-2 text-xl leading-6 text-gray-400"
|
||||
/>
|
||||
</template>
|
||||
</sw-avatar>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 sm:grid-col-1 md:grid-cols-2">
|
||||
<sw-input-group
|
||||
:label="$tc('settings.account_settings.name')"
|
||||
:error="nameError"
|
||||
>
|
||||
<sw-input
|
||||
v-model="formData.name"
|
||||
:invalid="$v.formData.name.$error"
|
||||
:placeholder="$t('settings.user_profile.name')"
|
||||
class="mt-2"
|
||||
@input="$v.formData.name.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$tc('settings.account_settings.email')"
|
||||
:error="emailError"
|
||||
>
|
||||
<sw-input
|
||||
v-model="formData.email"
|
||||
:invalid="$v.formData.email.$error"
|
||||
:placeholder="$t('settings.user_profile.email')"
|
||||
class="mt-2"
|
||||
@input="$v.formData.email.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$tc('settings.account_settings.password')"
|
||||
:error="passwordError"
|
||||
>
|
||||
<sw-input
|
||||
v-model="formData.password"
|
||||
:invalid="$v.formData.password.$error"
|
||||
:placeholder="$t('settings.user_profile.password')"
|
||||
type="password"
|
||||
class="mt-2"
|
||||
@input="$v.formData.password.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$tc('settings.account_settings.confirm_password')"
|
||||
:error="confirmPasswordError"
|
||||
class="mt-1 mb-2"
|
||||
>
|
||||
<sw-input
|
||||
v-model="formData.confirm_password"
|
||||
:invalid="$v.formData.confirm_password.$error"
|
||||
:placeholder="$t('settings.user_profile.confirm_password')"
|
||||
type="password"
|
||||
@input="$v.formData.confirm_password.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 mt-4 sm:grid-col-1 md:grid-cols-2">
|
||||
<sw-input-group
|
||||
:label="$tc('settings.language')"
|
||||
:error="languageError"
|
||||
>
|
||||
<sw-select
|
||||
v-model="language"
|
||||
:options="languages"
|
||||
:class="{ error: $v.language.$error }"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:allow-empty="false"
|
||||
:placeholder="$tc('settings.preferences.select_language')"
|
||||
class="mt-2"
|
||||
label="name"
|
||||
track-by="code"
|
||||
/>
|
||||
</sw-input-group>
|
||||
</div>
|
||||
|
||||
<sw-button
|
||||
class="mt-6"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
variant="primary"
|
||||
>
|
||||
<save-icon v-if="!isLoading" class="mr-2 -ml-1" />
|
||||
{{ $tc('settings.account_settings.save') }}
|
||||
</sw-button>
|
||||
</sw-card>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters, mapState } from 'vuex'
|
||||
import { CloudUploadIcon } from '@vue-hero-icons/solid'
|
||||
import BaseLoader from '../../components/base/BaseLoader.vue'
|
||||
|
||||
const {
|
||||
required,
|
||||
requiredIf,
|
||||
sameAs,
|
||||
email,
|
||||
minLength,
|
||||
} = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CloudUploadIcon,
|
||||
BaseLoader,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
name: null,
|
||||
email: null,
|
||||
password: null,
|
||||
confirm_password: null,
|
||||
},
|
||||
isLoading: false,
|
||||
previewAvatar: null,
|
||||
cropperOutputMime: '',
|
||||
fileObject: null,
|
||||
language: null,
|
||||
isRequestOnGoing: false,
|
||||
}
|
||||
},
|
||||
|
||||
validations: {
|
||||
formData: {
|
||||
name: {
|
||||
required,
|
||||
},
|
||||
email: {
|
||||
required,
|
||||
email,
|
||||
},
|
||||
password: {
|
||||
minLength: minLength(8),
|
||||
},
|
||||
confirm_password: {
|
||||
sameAsPassword: sameAs('password'),
|
||||
},
|
||||
},
|
||||
language: {
|
||||
required,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['languages']),
|
||||
|
||||
emailError() {
|
||||
if (!this.$v.formData.email.$error) {
|
||||
return ''
|
||||
}
|
||||
if (!this.$v.formData.email.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
if (!this.$v.formData.email.email) {
|
||||
return this.$tc('validation.email_incorrect')
|
||||
}
|
||||
},
|
||||
|
||||
passwordError() {
|
||||
if (!this.$v.formData.password.$error) {
|
||||
return ''
|
||||
}
|
||||
if (!this.$v.formData.password.minLength) {
|
||||
return this.$tc(
|
||||
'validation.password_min_length',
|
||||
this.$v.formData.password.$params.minLength.min,
|
||||
{ count: this.$v.formData.password.$params.minLength.min }
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
nameError() {
|
||||
if (!this.$v.formData.name.$error) {
|
||||
return ''
|
||||
}
|
||||
if (!this.$v.formData.name.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
|
||||
confirmPasswordError() {
|
||||
if (!this.$v.formData.confirm_password.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.formData.confirm_password.sameAsPassword) {
|
||||
return this.$tc('validation.password_incorrect')
|
||||
}
|
||||
},
|
||||
|
||||
languageError() {
|
||||
if (!this.$v.language.$error) {
|
||||
return ''
|
||||
}
|
||||
if (!this.$v.language.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
'formData.password'(val) {
|
||||
if (!val) {
|
||||
this.formData.confirm_password = ''
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.setInitialData()
|
||||
this.fetchLanguages()
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('user', [
|
||||
'fetchCurrentUser',
|
||||
'updateCurrentUser',
|
||||
'fetchUserSettings',
|
||||
'updateUserSettings',
|
||||
'uploadAvatar',
|
||||
]),
|
||||
|
||||
...mapActions(['fetchLanguages']),
|
||||
|
||||
onUploadHandler(cropper) {
|
||||
this.previewAvatar = cropper
|
||||
.getCroppedCanvas()
|
||||
.toDataURL(this.cropperOutputMime)
|
||||
},
|
||||
|
||||
onHandleUploadError() {
|
||||
window.toastr['error']('Oops! Something went wrong...')
|
||||
},
|
||||
|
||||
onChange(file) {
|
||||
this.cropperOutputMime = file.type
|
||||
this.fileObject = file
|
||||
},
|
||||
|
||||
async setInitialData() {
|
||||
this.isRequestOnGoing = true
|
||||
let response = await this.fetchCurrentUser()
|
||||
|
||||
this.formData.name = response.data.user.name
|
||||
this.formData.email = response.data.user.email
|
||||
|
||||
if (response.data.user.avatar) {
|
||||
this.previewAvatar = response.data.user.avatar
|
||||
} else {
|
||||
this.previewAvatar = '/images/default-avatar.jpg'
|
||||
}
|
||||
|
||||
let res = await this.fetchUserSettings(['language'])
|
||||
|
||||
this.language = this.languages.find(
|
||||
(language) => language.code == res.data.language
|
||||
)
|
||||
this.isRequestOnGoing = false
|
||||
},
|
||||
|
||||
async updateUserData() {
|
||||
this.$v.formData.$touch()
|
||||
|
||||
if (this.$v.$invalid) {
|
||||
return true
|
||||
}
|
||||
|
||||
this.isLoading = true
|
||||
|
||||
let data = {
|
||||
name: this.formData.name,
|
||||
email: this.formData.email,
|
||||
}
|
||||
|
||||
if (
|
||||
this.formData.password != null &&
|
||||
this.formData.password !== undefined &&
|
||||
this.formData.password !== ''
|
||||
) {
|
||||
data = { ...data, password: this.formData.password }
|
||||
}
|
||||
|
||||
let response = await this.updateCurrentUser(data)
|
||||
|
||||
let languageData = {
|
||||
settings: {
|
||||
language: this.language.code,
|
||||
},
|
||||
}
|
||||
|
||||
let languageRes = await this.updateUserSettings(languageData)
|
||||
|
||||
// if(languageRes) {
|
||||
// window.i18n.locale = this.language.code
|
||||
// }
|
||||
|
||||
if (response.data.success) {
|
||||
this.isLoading = false
|
||||
|
||||
if (this.fileObject && this.previewAvatar) {
|
||||
let avatarData = new FormData()
|
||||
|
||||
avatarData.append(
|
||||
'admin_avatar',
|
||||
JSON.stringify({
|
||||
name: this.fileObject.name,
|
||||
data: this.previewAvatar,
|
||||
})
|
||||
)
|
||||
this.uploadAvatar(avatarData)
|
||||
}
|
||||
|
||||
window.toastr['success'](
|
||||
this.$t('settings.account_settings.updated_message')
|
||||
)
|
||||
|
||||
this.formData.password = ''
|
||||
this.formData.confirm_password = ''
|
||||
return true
|
||||
}
|
||||
|
||||
window.toastr['error'](response.data.error)
|
||||
|
||||
this.isLoading = false
|
||||
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<transition name="fade">
|
||||
<div class="address-tab">
|
||||
<form action="" class="px-4 py-2" @submit.prevent="updateAddressSetting">
|
||||
<div class="grid grid-cols-12 mt-6">
|
||||
<div class="col-span-12 mb-6">
|
||||
<label class="text-sm font-medium leading-5 text-dark non-italic">
|
||||
{{
|
||||
$t('settings.customization.addresses.customer_billing_address')
|
||||
}}
|
||||
</label>
|
||||
<base-custom-input
|
||||
v-model="addresses.billing_address_format"
|
||||
:types="billingAddressType"
|
||||
class="mt-2"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-span-12 mb-6">
|
||||
<label class="text-sm font-medium leading-5 text-dark non-italic">
|
||||
{{
|
||||
$t('settings.customization.addresses.customer_shipping_address')
|
||||
}}
|
||||
</label>
|
||||
<base-custom-input
|
||||
v-model="addresses.shipping_address_format"
|
||||
:types="shippingAddressType"
|
||||
class="mt-2"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-span-12 mb-6">
|
||||
<label class="text-sm font-medium leading-5 text-dark non-italic">
|
||||
{{ $t('settings.customization.addresses.company_address') }}
|
||||
</label>
|
||||
<base-custom-input
|
||||
v-model="addresses.company_address_format"
|
||||
:types="companyAddressType"
|
||||
class="mt-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-12">
|
||||
<div class="col-span-12">
|
||||
<sw-button
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
>
|
||||
<save-icon v-if="!isLoading" class="mr-2" />
|
||||
{{ $t('settings.customization.save') }}
|
||||
</sw-button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
addresses: {
|
||||
billing_address_format: '',
|
||||
shipping_address_format: '',
|
||||
company_address_format: '',
|
||||
},
|
||||
billingAddressType: [
|
||||
{
|
||||
label: 'Customer',
|
||||
fields: [
|
||||
{ label: 'Display Name', value: 'CONTACT_DISPLAY_NAME' },
|
||||
{ label: 'Contact Name', value: 'PRIMARY_CONTACT_NAME' },
|
||||
{ label: 'Email', value: 'CONTACT_EMAIL' },
|
||||
{ label: 'Phone', value: 'CONTACT_PHONE' },
|
||||
{ label: 'Website', value: 'CONTACT_WEBSITE' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Billing Address',
|
||||
fields: [
|
||||
{ label: 'Adddress name', value: 'BILLING_ADDRESS_NAME' },
|
||||
{ label: 'Country', value: 'BILLING_COUNTRY' },
|
||||
{ label: 'State', value: 'BILLING_STATE' },
|
||||
{ label: 'City', value: 'BILLING_CITY' },
|
||||
{ label: 'Address Street 1', value: 'BILLING_ADDRESS_STREET_1' },
|
||||
{ label: 'Address Street 2', value: 'BILLING_ADDRESS_STREET_2' },
|
||||
{ label: 'Phone', value: 'BILLING_PHONE' },
|
||||
{ label: 'Zip Code', value: 'BILLING_ZIP_CODE' },
|
||||
],
|
||||
},
|
||||
],
|
||||
shippingAddressType: [
|
||||
{
|
||||
label: 'Customer',
|
||||
fields: [
|
||||
{ label: 'Display Name', value: 'CONTACT_DISPLAY_NAME' },
|
||||
{ label: 'Contact Name', value: 'PRIMARY_CONTACT_NAME' },
|
||||
{ label: 'Email', value: 'CONTACT_EMAIL' },
|
||||
{ label: 'Phone', value: 'CONTACT_PHONE' },
|
||||
{ label: 'Website', value: 'CONTACT_WEBSITE' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Shipping Address',
|
||||
fields: [
|
||||
{ label: 'Adddress name', value: 'SHIPPING_ADDRESS_NAME' },
|
||||
{ label: 'Country', value: 'SHIPPING_COUNTRY' },
|
||||
{ label: 'State', value: 'SHIPPING_STATE' },
|
||||
{ label: 'City', value: 'SHIPPING_CITY' },
|
||||
{ label: 'Address Street 1', value: 'SHIPPING_ADDRESS_STREET_1' },
|
||||
{ label: 'Address Street 2', value: 'SHIPPING_ADDRESS_STREET_2' },
|
||||
{ label: 'Phone', value: 'SHIPPING_PHONE' },
|
||||
{ label: 'Zip Code', value: 'SHIPPING_ZIP_CODE' },
|
||||
],
|
||||
},
|
||||
],
|
||||
companyAddressType: [
|
||||
{
|
||||
label: 'Company Address',
|
||||
fields: [
|
||||
{ label: 'Company Name', value: 'COMPANY_NAME' },
|
||||
{ label: 'Address street 1', value: 'COMPANY_ADDRESS_STREET_1' },
|
||||
{ label: 'Address Street 2', value: 'COMPANY_ADDRESS_STREET_2' },
|
||||
{ label: 'Country', value: 'COMPANY_COUNTRY' },
|
||||
{ label: 'State', value: 'COMPANY_STATE' },
|
||||
{ label: 'City', value: 'COMPANY_CITY' },
|
||||
{ label: 'Zip Code', value: 'COMPANY_ZIP_CODE' },
|
||||
{ label: 'Phone', value: 'COMPANY_PHONE' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async updateAddressSetting() {
|
||||
let data = { type: 'ADDRESSES', ...this.addresses, large: true }
|
||||
|
||||
// if (this.updateSetting(data)) {
|
||||
window.toastr['success'](
|
||||
this.$t('settings.customization.addresses.address_setting_updated')
|
||||
)
|
||||
// }
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,272 @@
|
||||
<template>
|
||||
<div>
|
||||
<form action="" class="mt-6" @submit.prevent="updateEstimateSetting">
|
||||
<sw-input-group
|
||||
:label="$t('settings.customization.estimates.estimate_prefix')"
|
||||
:error="estimatePrefixError"
|
||||
>
|
||||
<sw-input
|
||||
v-model="estimates.estimate_prefix"
|
||||
:invalid="$v.estimates.estimate_prefix.$error"
|
||||
style="max-width: 30%"
|
||||
@input="$v.estimates.estimate_prefix.$touch()"
|
||||
@keyup="changeToUppercase('ESTIMATES')"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="
|
||||
$t('settings.customization.estimates.default_estimate_email_body')
|
||||
"
|
||||
class="mt-6 mb-4"
|
||||
>
|
||||
<base-custom-input
|
||||
v-model="estimates.estimate_mail_body"
|
||||
:fields="mailFields"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.customization.estimates.company_address_format')"
|
||||
class="mt-6 mb-4"
|
||||
>
|
||||
<base-custom-input
|
||||
v-model="estimates.company_address_format"
|
||||
:fields="companyFields"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.customization.estimates.shipping_address_format')"
|
||||
class="mt-6 mb-4"
|
||||
>
|
||||
<base-custom-input
|
||||
v-model="estimates.shipping_address_format"
|
||||
:fields="shippingFields"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.customization.estimates.billing_address_format')"
|
||||
class="mt-6 mb-4"
|
||||
>
|
||||
<base-custom-input
|
||||
v-model="estimates.billing_address_format"
|
||||
:fields="billingFields"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-button
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
class="mt-4"
|
||||
>
|
||||
<save-icon v-if="!isLoading" class="mr-2" />
|
||||
{{ $t('settings.customization.save') }}
|
||||
</sw-button>
|
||||
</form>
|
||||
|
||||
<sw-divider class="mt-6 mb-8" />
|
||||
|
||||
<div class="flex">
|
||||
<div class="relative w-12">
|
||||
<sw-switch
|
||||
v-model="estimateAutogenerate"
|
||||
class="absolute"
|
||||
style="top: -20px"
|
||||
@change="setEstimateSetting"
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="p-0 mb-1 text-base leading-snug text-black">
|
||||
{{
|
||||
$t('settings.customization.estimates.autogenerate_estimate_number')
|
||||
}}
|
||||
</p>
|
||||
|
||||
<p
|
||||
class="p-0 m-0 text-xs leading-tight text-gray-500"
|
||||
style="max-width: 480px"
|
||||
>
|
||||
{{
|
||||
$t('settings.customization.estimates.estimate_setting_description')
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
const { required, maxLength, alpha } = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
props: {
|
||||
settings: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
estimateAutogenerate: false,
|
||||
|
||||
estimates: {
|
||||
estimate_prefix: null,
|
||||
estimate_mail_body: null,
|
||||
estimate_terms_and_conditions: null,
|
||||
company_address_format: null,
|
||||
shipping_address_format: null,
|
||||
billing_address_format: null,
|
||||
},
|
||||
billingFields: [
|
||||
'billing',
|
||||
'customer',
|
||||
'customerCustom',
|
||||
'estimateCustom',
|
||||
],
|
||||
shippingFields: [
|
||||
'shipping',
|
||||
'customer',
|
||||
'customerCustom',
|
||||
'estimateCustom',
|
||||
],
|
||||
mailFields: [
|
||||
'customer',
|
||||
'estimate',
|
||||
'company',
|
||||
'customerCustom',
|
||||
'estimateCustom',
|
||||
],
|
||||
companyFields: ['company', 'estimateCustom'],
|
||||
isLoading: false,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
estimatePrefixError() {
|
||||
if (!this.$v.estimates.estimate_prefix.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.estimates.estimate_prefix.required) {
|
||||
return this.$t('validation.required')
|
||||
}
|
||||
|
||||
if (!this.$v.estimates.estimate_prefix.maxLength) {
|
||||
return this.$t('validation.prefix_maxlength')
|
||||
}
|
||||
|
||||
if (!this.$v.estimates.estimate_prefix.alpha) {
|
||||
return this.$t('validation.characters_only')
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
validations: {
|
||||
estimates: {
|
||||
estimate_prefix: {
|
||||
required,
|
||||
maxLength: maxLength(5),
|
||||
alpha,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
settings(val) {
|
||||
this.estimates.estimate_prefix = val ? val.estimate_prefix : ''
|
||||
|
||||
this.estimates.estimate_mail_body = val ? val.estimate_mail_body : ''
|
||||
this.estimates.company_address_format = val
|
||||
? val.estimate_company_address_format
|
||||
: ''
|
||||
this.estimates.shipping_address_format = val
|
||||
? val.estimate_shipping_address_format
|
||||
: ''
|
||||
this.estimates.billing_address_format = val
|
||||
? val.estimate_billing_address_format
|
||||
: ''
|
||||
|
||||
this.estimates.estimate_terms_and_conditions = val
|
||||
? val.estimate_terms_and_conditions
|
||||
: ''
|
||||
|
||||
this.estimate_auto_generate = val ? val.estimate_auto_generate : ''
|
||||
|
||||
if (this.estimate_auto_generate === 'YES') {
|
||||
this.estimateAutogenerate = true
|
||||
} else {
|
||||
this.estimateAutogenerate = false
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('company', ['updateCompanySettings']),
|
||||
|
||||
async setEstimateSetting() {
|
||||
let data = {
|
||||
settings: {
|
||||
estimate_auto_generate: this.estimateAutogenerate ? 'YES' : 'NO',
|
||||
},
|
||||
}
|
||||
let response = await this.updateCompanySettings(data)
|
||||
if (response.data) {
|
||||
window.toastr['success'](this.$t('general.setting_updated'))
|
||||
}
|
||||
},
|
||||
|
||||
changeToUppercase(currentTab) {
|
||||
if (currentTab === 'ESTIMATES') {
|
||||
this.estimates.estimate_prefix = this.estimates.estimate_prefix.toUpperCase()
|
||||
return true
|
||||
}
|
||||
},
|
||||
|
||||
async updateEstimateSetting() {
|
||||
this.$v.estimates.$touch()
|
||||
|
||||
if (this.$v.estimates.$invalid) {
|
||||
return false
|
||||
}
|
||||
|
||||
let data = {
|
||||
settings: {
|
||||
estimate_prefix: this.estimates.estimate_prefix,
|
||||
estimate_mail_body: this.estimates.estimate_mail_body,
|
||||
estimate_company_address_format: this.estimates
|
||||
.company_address_format,
|
||||
estimate_shipping_address_format: this.estimates
|
||||
.shipping_address_format,
|
||||
estimate_billing_address_format: this.estimates
|
||||
.billing_address_format,
|
||||
},
|
||||
}
|
||||
|
||||
if (this.updateSetting(data)) {
|
||||
window.toastr['success'](
|
||||
this.$t('settings.customization.estimates.estimate_setting_updated')
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
async updateSetting(data) {
|
||||
this.isLoading = true
|
||||
let res = await this.updateCompanySettings(data)
|
||||
|
||||
if (res.data.success) {
|
||||
this.isLoading = false
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<div>
|
||||
<form action="" class="mt-6" @submit.prevent="updateInvoiceSetting">
|
||||
<sw-input-group
|
||||
:label="$t('settings.customization.invoices.invoice_prefix')"
|
||||
:error="invoicePrefixError"
|
||||
>
|
||||
<sw-input
|
||||
v-model="invoices.invoice_prefix"
|
||||
:invalid="$v.invoices.invoice_prefix.$error"
|
||||
style="max-width: 30%"
|
||||
@input="$v.invoices.invoice_prefix.$touch()"
|
||||
@keyup="changeToUppercase('INVOICES')"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="
|
||||
$t('settings.customization.invoices.default_invoice_email_body')
|
||||
"
|
||||
class="mt-6 mb-4"
|
||||
>
|
||||
<base-custom-input
|
||||
v-model="invoices.invoice_mail_body"
|
||||
:fields="InvoiceMailFields"
|
||||
class="mt-2"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.customization.invoices.company_address_format')"
|
||||
class="mt-6 mb-4"
|
||||
>
|
||||
<base-custom-input
|
||||
v-model="invoices.company_address_format"
|
||||
:fields="companyFields"
|
||||
class="mt-2"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.customization.invoices.shipping_address_format')"
|
||||
class="mt-6 mb-4"
|
||||
>
|
||||
<base-custom-input
|
||||
v-model="invoices.shipping_address_format"
|
||||
:fields="shippingFields"
|
||||
class="mt-2"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.customization.invoices.billing_address_format')"
|
||||
class="mt-6 mb-4"
|
||||
>
|
||||
<base-custom-input
|
||||
v-model="invoices.billing_address_format"
|
||||
:fields="billingFields"
|
||||
class="mt-2"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-button
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
class="mt-4"
|
||||
>
|
||||
<save-icon v-if="!isLoading" class="mr-2" />
|
||||
{{ $t('settings.customization.save') }}
|
||||
</sw-button>
|
||||
</form>
|
||||
|
||||
<sw-divider class="mt-6 mb-8" />
|
||||
|
||||
<div class="flex">
|
||||
<div class="relative w-12">
|
||||
<sw-switch
|
||||
v-model="invoiceAutogenerate"
|
||||
class="absolute"
|
||||
style="top: -20px"
|
||||
@change="setInvoiceSetting"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ml-4">
|
||||
<p class="p-0 mb-1 text-base leading-snug text-black">
|
||||
{{
|
||||
$t('settings.customization.invoices.autogenerate_invoice_number')
|
||||
}}
|
||||
</p>
|
||||
|
||||
<p
|
||||
class="p-0 m-0 text-xs leading-tight text-gray-500"
|
||||
style="max-width: 480px"
|
||||
>
|
||||
{{
|
||||
$t('settings.customization.invoices.invoice_setting_description')
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const { required, maxLength, alpha } = require('vuelidate/lib/validators')
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
settings: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
invoiceAutogenerate: false,
|
||||
|
||||
invoices: {
|
||||
invoice_prefix: null,
|
||||
invoice_mail_body: null,
|
||||
company_address_format: null,
|
||||
shipping_address_format: null,
|
||||
billing_address_format: null,
|
||||
},
|
||||
isLoading: false,
|
||||
InvoiceMailFields: [
|
||||
'customer',
|
||||
'customerCustom',
|
||||
'invoice',
|
||||
'invoiceCustom',
|
||||
'company',
|
||||
],
|
||||
billingFields: ['billing', 'customer', 'customerCustom', 'invoiceCustom'],
|
||||
shippingFields: [
|
||||
'shipping',
|
||||
'customer',
|
||||
'customerCustom',
|
||||
'invoiceCustom',
|
||||
],
|
||||
companyFields: ['company', 'invoiceCustom'],
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
invoicePrefixError() {
|
||||
if (!this.$v.invoices.invoice_prefix.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.invoices.invoice_prefix.required) {
|
||||
return this.$t('validation.required')
|
||||
}
|
||||
|
||||
if (!this.$v.invoices.invoice_prefix.maxLength) {
|
||||
return this.$t('validation.prefix_maxlength')
|
||||
}
|
||||
|
||||
if (!this.$v.invoices.invoice_prefix.alpha) {
|
||||
return this.$t('validation.characters_only')
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
settings(val) {
|
||||
this.invoices.invoice_prefix = val ? val.invoice_prefix : ''
|
||||
|
||||
this.invoices.invoice_mail_body = val ? val.invoice_mail_body : null
|
||||
this.invoices.company_address_format = val
|
||||
? val.invoice_company_address_format
|
||||
: null
|
||||
this.invoices.shipping_address_format = val
|
||||
? val.invoice_shipping_address_format
|
||||
: null
|
||||
this.invoices.billing_address_format = val
|
||||
? val.invoice_billing_address_format
|
||||
: null
|
||||
|
||||
this.invoice_auto_generate = val ? val.invoice_auto_generate : ''
|
||||
|
||||
if (this.invoice_auto_generate === 'YES') {
|
||||
this.invoiceAutogenerate = true
|
||||
} else {
|
||||
this.invoiceAutogenerate = false
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
validations: {
|
||||
invoices: {
|
||||
invoice_prefix: {
|
||||
required,
|
||||
maxLength: maxLength(5),
|
||||
alpha,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('company', ['updateCompanySettings']),
|
||||
|
||||
async setInvoiceSetting() {
|
||||
let data = {
|
||||
settings: {
|
||||
invoice_auto_generate: this.invoiceAutogenerate ? 'YES' : 'NO',
|
||||
},
|
||||
}
|
||||
|
||||
let response = await this.updateCompanySettings(data)
|
||||
|
||||
if (response.data) {
|
||||
window.toastr['success'](this.$t('general.setting_updated'))
|
||||
}
|
||||
},
|
||||
|
||||
changeToUppercase(currentTab) {
|
||||
if (currentTab === 'INVOICES') {
|
||||
this.invoices.invoice_prefix = this.invoices.invoice_prefix.toUpperCase()
|
||||
|
||||
return true
|
||||
}
|
||||
},
|
||||
|
||||
async updateInvoiceSetting() {
|
||||
this.$v.invoices.$touch()
|
||||
|
||||
if (this.$v.invoices.$invalid) {
|
||||
return false
|
||||
}
|
||||
|
||||
let data = {
|
||||
settings: {
|
||||
invoice_prefix: this.invoices.invoice_prefix,
|
||||
invoice_mail_body: this.invoices.invoice_mail_body,
|
||||
invoice_company_address_format: this.invoices.company_address_format,
|
||||
invoice_billing_address_format: this.invoices.billing_address_format,
|
||||
invoice_shipping_address_format: this.invoices
|
||||
.shipping_address_format,
|
||||
},
|
||||
}
|
||||
|
||||
if (this.updateSetting(data)) {
|
||||
window.toastr['success'](
|
||||
this.$t('settings.customization.invoices.invoice_setting_updated')
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
async updateSetting(data) {
|
||||
this.isLoading = true
|
||||
let res = await this.updateCompanySettings(data)
|
||||
|
||||
if (res.data.success) {
|
||||
this.isLoading = false
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-wrap justify-end mt-8 lg:flex-no-wrap">
|
||||
<sw-button size="lg" variant="primary-outline" @click="addItemUnit">
|
||||
<plus-icon class="w-6 h-6 mr-1 -ml-2" />
|
||||
{{ $t('settings.customization.items.add_item_unit') }}
|
||||
</sw-button>
|
||||
</div>
|
||||
|
||||
<sw-table-component
|
||||
ref="table"
|
||||
variant="gray"
|
||||
:data="fetchData"
|
||||
:show-filter="false"
|
||||
>
|
||||
<sw-table-column
|
||||
:sortable="true"
|
||||
:label="$t('settings.customization.items.unit_name')"
|
||||
show="name"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.customization.items.unit_name') }}</span>
|
||||
<span class="mt-6">{{ row.name }}</span>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
|
||||
<sw-table-column
|
||||
:sortable="false"
|
||||
:filterable="false"
|
||||
cell-class="action-dropdown"
|
||||
>
|
||||
<template slot-scope="row">
|
||||
<span>{{ $t('settings.tax_types.action') }}</span>
|
||||
<sw-dropdown>
|
||||
<dot-icon slot="activator" class="h-5 mr-3 text-primary-800" />
|
||||
|
||||
<sw-dropdown-item @click="editItemUnit(row)">
|
||||
<pencil-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('general.edit') }}
|
||||
</sw-dropdown-item>
|
||||
|
||||
<sw-dropdown-item @click="removeItemUnit(row.id)">
|
||||
<trash-icon class="h-5 mr-3 text-gray-600" />
|
||||
{{ $t('general.delete') }}
|
||||
</sw-dropdown-item>
|
||||
</sw-dropdown>
|
||||
</template>
|
||||
</sw-table-column>
|
||||
</sw-table-component>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import { TrashIcon, PencilIcon, PlusIcon } from '@vue-hero-icons/solid'
|
||||
const { required, maxLength, alpha } = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TrashIcon,
|
||||
PlusIcon,
|
||||
PencilIcon,
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('modal', ['openModal']),
|
||||
|
||||
...mapActions('item', ['deleteItemUnit', 'fetchItemUnits']),
|
||||
|
||||
async fetchData({ page, filter, sort }) {
|
||||
let data = {
|
||||
orderByField: sort.fieldName || 'created_at',
|
||||
orderBy: sort.order || 'desc',
|
||||
page,
|
||||
}
|
||||
|
||||
let response = await this.fetchItemUnits(data)
|
||||
|
||||
return {
|
||||
data: response.data.units.data,
|
||||
pagination: {
|
||||
totalPages: response.data.units.last_page,
|
||||
currentPage: page,
|
||||
count: response.data.units.count,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
async addItemUnit() {
|
||||
this.openModal({
|
||||
title: this.$t('settings.customization.items.add_item_unit'),
|
||||
componentName: 'ItemUnit',
|
||||
refreshData: this.$refs.table.refresh,
|
||||
})
|
||||
},
|
||||
|
||||
async editItemUnit(data) {
|
||||
this.openModal({
|
||||
title: this.$t('settings.customization.items.edit_item_unit'),
|
||||
componentName: 'ItemUnit',
|
||||
id: data.id,
|
||||
data: data,
|
||||
refreshData: this.$refs.table.refresh,
|
||||
})
|
||||
},
|
||||
|
||||
async removeItemUnit(id) {
|
||||
swal({
|
||||
title: this.$t('general.are_you_sure'),
|
||||
text: this.$t('settings.customization.items.item_unit_confirm_delete'),
|
||||
icon: '/assets/icon/trash-solid.svg',
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
}).then(async (value) => {
|
||||
if (value) {
|
||||
let response = await this.deleteItemUnit(id)
|
||||
|
||||
if (response.data.success) {
|
||||
window.toastr['success'](
|
||||
this.$t('settings.customization.items.deleted_message')
|
||||
)
|
||||
this.$refs.table.refresh()
|
||||
return true
|
||||
}
|
||||
window.toastr['error'](
|
||||
this.$t('settings.customization.items.already_in_use')
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<div>
|
||||
<form action="" class="mt-6" @submit.prevent="updatePaymentSetting">
|
||||
<sw-input-group
|
||||
:label="$t('settings.customization.payments.payment_prefix')"
|
||||
:error="paymentPrefixError"
|
||||
>
|
||||
<sw-input
|
||||
v-model="payments.payment_prefix"
|
||||
:invalid="$v.payments.payment_prefix.$error"
|
||||
class="mt-2"
|
||||
style="max-width: 30%"
|
||||
@input="$v.payments.payment_prefix.$touch()"
|
||||
@keyup="changeToUppercase('PAYMENTS')"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="
|
||||
$t('settings.customization.payments.default_payment_email_body')
|
||||
"
|
||||
class="mt-6 mb-4"
|
||||
>
|
||||
<base-custom-input
|
||||
v-model="payments.payment_mail_body"
|
||||
:fields="mailFields"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.customization.payments.company_address_format')"
|
||||
class="mt-6 mb-4"
|
||||
>
|
||||
<base-custom-input
|
||||
v-model="payments.company_address_format"
|
||||
:fields="companyFields"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="
|
||||
$t('settings.customization.payments.from_customer_address_format')
|
||||
"
|
||||
class="mt-6 mb-4"
|
||||
>
|
||||
<base-custom-input
|
||||
v-model="payments.from_customer_address_format"
|
||||
:fields="customerAddressFields"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-button
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
class="my-4"
|
||||
>
|
||||
<save-icon v-if="!isLoading" class="mr-2" />
|
||||
{{ $t('settings.customization.save') }}
|
||||
</sw-button>
|
||||
</form>
|
||||
|
||||
<sw-divider class="mt-6 mb-8" />
|
||||
|
||||
<div class="flex">
|
||||
<div class="relative w-12">
|
||||
<sw-switch
|
||||
v-model="paymentAutogenerate"
|
||||
class="absolute"
|
||||
style="top: -20px"
|
||||
@change="setPaymentSetting"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ml-4">
|
||||
<p class="p-0 mb-1 text-base leading-snug text-black">
|
||||
{{
|
||||
$t('settings.customization.payments.autogenerate_payment_number')
|
||||
}}
|
||||
</p>
|
||||
|
||||
<p
|
||||
class="p-0 m-0 text-xs leading-tight text-gray-500"
|
||||
style="max-width: 480px"
|
||||
>
|
||||
{{
|
||||
$t('settings.customization.payments.payment_setting_description')
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
const { required, maxLength, alpha } = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
props: {
|
||||
settings: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
paymentAutogenerate: false,
|
||||
|
||||
payments: {
|
||||
payment_prefix: null,
|
||||
payment_mail_body: null,
|
||||
from_customer_address_format: null,
|
||||
company_address_format: null,
|
||||
},
|
||||
|
||||
mailFields: [
|
||||
'customer',
|
||||
'customerCustom',
|
||||
'company',
|
||||
'payment',
|
||||
'paymentCustom',
|
||||
],
|
||||
customerAddressFields: [
|
||||
'billing',
|
||||
'customer',
|
||||
'customerCustom',
|
||||
'paymentCustom',
|
||||
],
|
||||
companyFields: ['company', 'paymentCustom'],
|
||||
isLoading: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
paymentPrefixError() {
|
||||
if (!this.$v.payments.payment_prefix.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.payments.payment_prefix.required) {
|
||||
return this.$t('validation.required')
|
||||
}
|
||||
|
||||
if (!this.$v.payments.payment_prefix.maxLength) {
|
||||
return this.$t('validation.prefix_maxlength')
|
||||
}
|
||||
|
||||
if (!this.$v.payments.payment_prefix.alpha) {
|
||||
return this.$t('validation.characters_only')
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
validations: {
|
||||
payments: {
|
||||
payment_prefix: {
|
||||
required,
|
||||
maxLength: maxLength(5),
|
||||
alpha,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
settings(val) {
|
||||
this.payments.payment_prefix = val ? val.payment_prefix : ''
|
||||
|
||||
this.payments.payment_mail_body = val ? val.payment_mail_body : ''
|
||||
|
||||
this.payments.company_address_format = val
|
||||
? val.payment_company_address_format
|
||||
: ''
|
||||
|
||||
this.payments.from_customer_address_format = val
|
||||
? val.payment_from_customer_address_format
|
||||
: ''
|
||||
|
||||
this.payment_auto_generate = val ? val.payment_auto_generate : ''
|
||||
|
||||
if (this.payment_auto_generate === 'YES') {
|
||||
this.paymentAutogenerate = true
|
||||
} else {
|
||||
this.paymentAutogenerate = false
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('modal', ['openModal']),
|
||||
|
||||
...mapActions('company', ['updateCompanySettings']),
|
||||
|
||||
changeToUppercase(currentTab) {
|
||||
if (currentTab === 'PAYMENTS') {
|
||||
this.payments.payment_prefix = this.payments.payment_prefix.toUpperCase()
|
||||
return true
|
||||
}
|
||||
},
|
||||
|
||||
async setPaymentSetting() {
|
||||
let data = {
|
||||
settings: {
|
||||
payment_auto_generate: this.paymentAutogenerate ? 'YES' : 'NO',
|
||||
},
|
||||
}
|
||||
let response = await this.updateCompanySettings(data)
|
||||
if (response.data) {
|
||||
window.toastr['success'](this.$t('general.setting_updated'))
|
||||
}
|
||||
},
|
||||
|
||||
async updatePaymentSetting() {
|
||||
this.$v.payments.$touch()
|
||||
|
||||
if (this.$v.payments.$invalid) {
|
||||
return false
|
||||
}
|
||||
|
||||
let data = {
|
||||
settings: {
|
||||
payment_prefix: this.payments.payment_prefix,
|
||||
payment_mail_body: this.payments.payment_mail_body,
|
||||
payment_company_address_format: this.payments.company_address_format,
|
||||
payment_from_customer_address_format: this.payments
|
||||
.from_customer_address_format,
|
||||
},
|
||||
}
|
||||
|
||||
if (this.updateSetting(data)) {
|
||||
window.toastr['success'](
|
||||
this.$t('settings.customization.payments.payment_setting_updated')
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
async updateSetting(data) {
|
||||
this.isLoading = true
|
||||
let res = await this.updateCompanySettings(data)
|
||||
|
||||
if (res.data.success) {
|
||||
this.isLoading = false
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,111 +0,0 @@
|
||||
<template>
|
||||
<div class="invoice-create-page main-content">
|
||||
<div class="page-header">
|
||||
<h3 class="page-title">{{ $tc('settings.setting',1) }}</h3>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><router-link slot="item-title" to="/admin/dashboard">{{ $t('general.home') }}</router-link></li>
|
||||
<li class="breadcrumb-item"><router-link slot="item-title" to="/admin/settings/user-profile">{{ $tc('settings.setting', 2) }}</router-link></li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="row settings-container">
|
||||
<div class="col-lg-3 settings-sidebar-container">
|
||||
<ol class="settings-sidebar">
|
||||
<li v-for="(menuItem, index) in menuItems" :key="index" class="settings-menu-item">
|
||||
<router-link :class="['link-color', {'active-setting': hasActiveUrl(menuItem.link)}]" :to="menuItem.link">
|
||||
<font-awesome-icon :icon="[menuItem.iconType, menuItem.icon]" class="setting-icon"/>
|
||||
<span class="menu-title ml-3">{{ $t(menuItem.title) }}</span>
|
||||
</router-link>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<transition
|
||||
name="fade"
|
||||
mode="out-in">
|
||||
<router-view/>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
menuItems: [
|
||||
{
|
||||
link: '/admin/settings/user-profile',
|
||||
title: 'settings.menu_title.account_settings',
|
||||
icon: 'user',
|
||||
iconType: 'far'
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/company-info',
|
||||
title: 'settings.menu_title.company_information',
|
||||
icon: 'building',
|
||||
iconType: 'far'
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/customization',
|
||||
title: 'settings.menu_title.customization',
|
||||
icon: 'edit',
|
||||
iconType: 'fa'
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/preferences',
|
||||
title: 'settings.menu_title.preferences',
|
||||
icon: 'cog',
|
||||
iconType: 'fas'
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/tax-types',
|
||||
title: 'settings.menu_title.tax_types',
|
||||
icon: 'check-circle',
|
||||
iconType: 'far'
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/expense-category',
|
||||
title: 'settings.menu_title.expense_category',
|
||||
icon: 'list-alt',
|
||||
iconType: 'far'
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/mail-configuration',
|
||||
title: 'settings.mail.mail_config',
|
||||
icon: 'envelope',
|
||||
iconType: 'fa'
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/notifications',
|
||||
title: 'settings.menu_title.notifications',
|
||||
icon: 'bell',
|
||||
iconType: 'far'
|
||||
},
|
||||
{
|
||||
link: '/admin/settings/update-app',
|
||||
title: 'settings.menu_title.update_app',
|
||||
icon: 'sync-alt',
|
||||
iconType: 'fas'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route.path' (newValue) {
|
||||
if (newValue === '/admin/settings') {
|
||||
this.$router.push('/admin/settings/user-profile')
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.$route.path === '/admin/settings') {
|
||||
this.$router.push('/admin/settings/user-profile')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hasActiveUrl (url) {
|
||||
return this.$route.path.indexOf(url) > -1
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveEmailConfig">
|
||||
<div class="grid gap-6 grid-col-1 md:grid-cols-2">
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.driver')"
|
||||
:error="driverError"
|
||||
required
|
||||
>
|
||||
<sw-select
|
||||
v-model="mailConfigData.mail_driver"
|
||||
:invalid="$v.mailConfigData.mail_driver.$error"
|
||||
:options="mailDrivers"
|
||||
:searchable="true"
|
||||
:allow-empty="false"
|
||||
:show-labels="false"
|
||||
class="mt-2"
|
||||
@input="onChangeDriver"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.from_mail')"
|
||||
:error="fromEmailError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.from_mail.$error"
|
||||
v-model.trim="mailConfigData.from_mail"
|
||||
type="text"
|
||||
name="from_mail"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.from_mail.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.from_name')"
|
||||
:error="fromNameError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.from_name.$error"
|
||||
v-model.trim="mailConfigData.from_name"
|
||||
type="text"
|
||||
name="name"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.from_name.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
</div>
|
||||
<div class="flex mt-8">
|
||||
<sw-button
|
||||
:disabled="loading"
|
||||
:loading="loading"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
>
|
||||
<save-icon class="mr-2" />
|
||||
{{ $t('general.save') }}
|
||||
</sw-button>
|
||||
<slot />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const { required, email } = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
props: {
|
||||
configData: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
mailDrivers: {
|
||||
type: Array,
|
||||
require: true,
|
||||
default: Array,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mailConfigData: {
|
||||
mail_driver: '',
|
||||
mail_host: '',
|
||||
from_mail: '',
|
||||
from_name: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
mailConfigData: {
|
||||
mail_driver: {
|
||||
required,
|
||||
},
|
||||
from_mail: {
|
||||
required,
|
||||
email,
|
||||
},
|
||||
from_name: {
|
||||
required,
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
driverError() {
|
||||
if (!this.$v.mailConfigData.mail_driver.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.mail_driver.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
fromEmailError() {
|
||||
if (!this.$v.mailConfigData.from_mail.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.from_mail.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.from_mail.email) {
|
||||
return this.$tc('validation.email_incorrect')
|
||||
}
|
||||
},
|
||||
fromNameError() {
|
||||
if (!this.$v.mailConfigData.from_name.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.from_name.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
for (const key in this.mailConfigData) {
|
||||
if (this.configData.hasOwnProperty(key)) {
|
||||
this.mailConfigData[key] = this.configData[key]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async saveEmailConfig() {
|
||||
this.$v.mailConfigData.$touch()
|
||||
if (!this.$v.mailConfigData.$invalid) {
|
||||
this.$emit('submit-data', this.mailConfigData)
|
||||
}
|
||||
return false
|
||||
},
|
||||
onChangeDriver() {
|
||||
this.$v.mailConfigData.mail_driver.$touch()
|
||||
this.$emit('on-change-driver', this.mailConfigData.mail_driver)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveEmailConfig">
|
||||
<div class="grid gap-6 sm:grid-col-1 md:grid-cols-2">
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.driver')"
|
||||
:error="driverError"
|
||||
required
|
||||
>
|
||||
<sw-select
|
||||
v-model="mailConfigData.mail_driver"
|
||||
:invalid="$v.mailConfigData.mail_driver.$error"
|
||||
:options="mailDrivers"
|
||||
:allow-empty="false"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
class="mt-2"
|
||||
@input="onChangeDriver"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.mailgun_domain')"
|
||||
:error="domainError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.mail_mailgun_domain.$error"
|
||||
v-model.trim="mailConfigData.mail_mailgun_domain"
|
||||
type="text"
|
||||
name="mailgun_domain"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.mail_mailgun_domain.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.mailgun_secret')"
|
||||
:error="secretError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.mail_mailgun_secret.$error"
|
||||
v-model.trim="mailConfigData.mail_mailgun_secret"
|
||||
:type="getInputType"
|
||||
name="mailgun_secret"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.mail_mailgun_secret.$touch()"
|
||||
>
|
||||
<template v-slot:rightIcon>
|
||||
<eye-off-icon
|
||||
v-if="isShowPassword"
|
||||
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
<eye-icon
|
||||
v-else
|
||||
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
</template>
|
||||
</sw-input>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.mailgun_endpoint')"
|
||||
:error="endpointError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.mail_mailgun_endpoint.$error"
|
||||
v-model.trim="mailConfigData.mail_mailgun_endpoint"
|
||||
type="text"
|
||||
name="mailgun_endpoint"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.mail_mailgun_endpoint.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.from_mail')"
|
||||
:error="fromEmailError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.from_mail.$error"
|
||||
v-model.trim="mailConfigData.from_mail"
|
||||
type="text"
|
||||
name="from_mail"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.from_mail.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.from_name')"
|
||||
:error="fromNameError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.from_name.$error"
|
||||
v-model.trim="mailConfigData.from_name"
|
||||
type="text"
|
||||
name="from_name"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.from_name.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
</div>
|
||||
<div class="flex my-10">
|
||||
<sw-button
|
||||
:disabled="loading"
|
||||
:loading="loading"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
>
|
||||
<save-icon class="mr-2" />
|
||||
{{ $t('general.save') }}
|
||||
</sw-button>
|
||||
<slot />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const { required, email, numeric } = require('vuelidate/lib/validators')
|
||||
import { EyeIcon, EyeOffIcon } from '@vue-hero-icons/outline'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
configData: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
mailDrivers: {
|
||||
type: Array,
|
||||
require: true,
|
||||
default: Array,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
EyeIcon,
|
||||
EyeOffIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShowPassword: false,
|
||||
mailConfigData: {
|
||||
mail_driver: '',
|
||||
mail_mailgun_domain: '',
|
||||
mail_mailgun_secret: '',
|
||||
mail_mailgun_endpoint: '',
|
||||
from_mail: '',
|
||||
from_name: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
mailConfigData: {
|
||||
mail_driver: {
|
||||
required,
|
||||
},
|
||||
mail_mailgun_domain: {
|
||||
required,
|
||||
},
|
||||
mail_mailgun_endpoint: {
|
||||
required,
|
||||
},
|
||||
mail_mailgun_secret: {
|
||||
required,
|
||||
},
|
||||
from_mail: {
|
||||
required,
|
||||
email,
|
||||
},
|
||||
from_name: {
|
||||
required,
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
driverError() {
|
||||
if (!this.$v.mailConfigData.mail_driver.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.mail_driver.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
domainError() {
|
||||
if (!this.$v.mailConfigData.mail_mailgun_domain.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.mail_mailgun_domain.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
secretError() {
|
||||
if (!this.$v.mailConfigData.mail_mailgun_secret.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.mail_mailgun_secret.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
endpointError() {
|
||||
if (!this.$v.mailConfigData.mail_mailgun_endpoint.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.mail_mailgun_endpoint.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.mail_mailgun_endpoint.numeric) {
|
||||
return this.$tc('validation.numbers_only')
|
||||
}
|
||||
},
|
||||
fromEmailError() {
|
||||
if (!this.$v.mailConfigData.from_mail.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.from_mail.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.from_mail.email) {
|
||||
return this.$tc('validation.email_incorrect')
|
||||
}
|
||||
},
|
||||
fromNameError() {
|
||||
if (!this.$v.mailConfigData.from_name.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.from_name.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
getInputType() {
|
||||
if (this.isShowPassword) {
|
||||
return 'text'
|
||||
}
|
||||
return 'password'
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
for (const key in this.mailConfigData) {
|
||||
if (this.configData.hasOwnProperty(key)) {
|
||||
this.mailConfigData[key] = this.configData[key]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async saveEmailConfig() {
|
||||
this.$v.mailConfigData.$touch()
|
||||
if (!this.$v.mailConfigData.$invalid) {
|
||||
this.$emit('submit-data', this.mailConfigData)
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
onChangeDriver() {
|
||||
this.$v.mailConfigData.mail_driver.$touch()
|
||||
this.$emit('on-change-driver', this.mailConfigData.mail_driver)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
334
resources/assets/js/views/settings/mail-driver/SesMailDriver.vue
Normal file
334
resources/assets/js/views/settings/mail-driver/SesMailDriver.vue
Normal file
@@ -0,0 +1,334 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveEmailConfig">
|
||||
<div class="grid gap-6 sm:grid-col-1 md:grid-cols-2">
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.driver')"
|
||||
:error="driverError"
|
||||
required
|
||||
>
|
||||
<sw-select
|
||||
v-model="mailConfigData.mail_driver"
|
||||
:invalid="$v.mailConfigData.mail_driver.$error"
|
||||
:options="mailDrivers"
|
||||
:allow-empty="false"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
class="mt-2"
|
||||
@input="onChangeDriver"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.host')"
|
||||
:error="hostError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.mail_host.$error"
|
||||
v-model.trim="mailConfigData.mail_host"
|
||||
type="text"
|
||||
name="mail_host"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.mail_host.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.port')"
|
||||
:error="portError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.mail_port.$error"
|
||||
v-model.trim="mailConfigData.mail_port"
|
||||
type="text"
|
||||
name="mail_port"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.mail_port.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.encryption')"
|
||||
:error="encryptionError"
|
||||
required
|
||||
>
|
||||
<sw-select
|
||||
v-model.trim="mailConfigData.mail_encryption"
|
||||
:invalid="$v.mailConfigData.mail_encryption.$error"
|
||||
:options="encryptions"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.mail_encryption.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.from_mail')"
|
||||
:error="fromEmailError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.from_mail.$error"
|
||||
v-model.trim="mailConfigData.from_mail"
|
||||
type="text"
|
||||
name="from_mail"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.from_mail.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.from_name')"
|
||||
:error="fromNameError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.from_name.$error"
|
||||
v-model.trim="mailConfigData.from_name"
|
||||
type="text"
|
||||
name="name"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.from_name.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.ses_key')"
|
||||
:error="keyError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.mail_ses_key.$error"
|
||||
v-model.trim="mailConfigData.mail_ses_key"
|
||||
type="text"
|
||||
name="mail_ses_key"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.mail_ses_key.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.ses_secret')"
|
||||
:error="secretError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.mail_ses_secret.$error"
|
||||
v-model.trim="mailConfigData.mail_ses_secret"
|
||||
:type="getInputType"
|
||||
name="mail_ses_secret"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.mail_ses_secret.$touch()"
|
||||
>
|
||||
<template v-slot:rightIcon>
|
||||
<eye-off-icon
|
||||
v-if="isShowPassword"
|
||||
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
<eye-icon
|
||||
v-else
|
||||
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
</template>
|
||||
</sw-input>
|
||||
</sw-input-group>
|
||||
</div>
|
||||
<div class="flex my-10">
|
||||
<sw-button
|
||||
:disabled="loading"
|
||||
:loading="loading"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
>
|
||||
<save-icon class="mr-2" />
|
||||
{{ $t('general.save') }}
|
||||
</sw-button>
|
||||
<slot />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const { required, email, numeric } = require('vuelidate/lib/validators')
|
||||
import { EyeIcon, EyeOffIcon } from '@vue-hero-icons/outline'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
configData: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
mailDrivers: {
|
||||
type: Array,
|
||||
require: true,
|
||||
default: Array,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
EyeOffIcon,
|
||||
EyeIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShowPassword: false,
|
||||
mailConfigData: {
|
||||
mail_driver: '',
|
||||
mail_host: '',
|
||||
mail_port: null,
|
||||
mail_ses_key: '',
|
||||
mail_ses_secret: '',
|
||||
mail_encryption: 'tls',
|
||||
from_mail: '',
|
||||
from_name: '',
|
||||
},
|
||||
encryptions: ['tls', 'ssl', 'starttls'],
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
mailConfigData: {
|
||||
mail_driver: {
|
||||
required,
|
||||
},
|
||||
mail_host: {
|
||||
required,
|
||||
},
|
||||
mail_port: {
|
||||
required,
|
||||
numeric,
|
||||
},
|
||||
mail_ses_key: {
|
||||
required,
|
||||
},
|
||||
mail_ses_secret: {
|
||||
required,
|
||||
},
|
||||
mail_encryption: {
|
||||
required,
|
||||
},
|
||||
from_mail: {
|
||||
required,
|
||||
email,
|
||||
},
|
||||
from_name: {
|
||||
required,
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
secretError() {
|
||||
if (!this.$v.mailConfigData.mail_ses_secret.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.mail_ses_secret.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
keyError() {
|
||||
if (!this.$v.mailConfigData.mail_ses_key.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.mail_ses_key.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
fromNameError() {
|
||||
if (!this.$v.mailConfigData.from_name.$error) {
|
||||
return ''
|
||||
}
|
||||
if (!this.$v.mailConfigData.from_name.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
fromEmailError() {
|
||||
if (!this.$v.mailConfigData.from_mail.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.from_mail.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.from_mail.email) {
|
||||
return this.$tc('validation.email_incorrect')
|
||||
}
|
||||
},
|
||||
encryptionError() {
|
||||
if (!this.$v.mailConfigData.mail_encryption.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.mail_encryption.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
portError() {
|
||||
if (!this.$v.mailConfigData.mail_port.$error) {
|
||||
return ''
|
||||
}
|
||||
if (!this.$v.mailConfigData.mail_port.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.mail_port.numeric) {
|
||||
return this.$tc('validation.numbers_only')
|
||||
}
|
||||
},
|
||||
hostError() {
|
||||
if (!this.$v.mailConfigData.mail_host.$error) {
|
||||
return ''
|
||||
}
|
||||
if (!this.$v.mailConfigData.mail_host.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
driverError() {
|
||||
if (!this.$v.mailConfigData.mail_driver.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.mail_driver.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
getInputType() {
|
||||
if (this.isShowPassword) {
|
||||
return 'text'
|
||||
}
|
||||
return 'password'
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
for (const key in this.mailConfigData) {
|
||||
if (this.configData.hasOwnProperty(key)) {
|
||||
this.mailConfigData[key] = this.configData[key]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async saveEmailConfig() {
|
||||
this.$v.mailConfigData.$touch()
|
||||
if (!this.$v.mailConfigData.$invalid) {
|
||||
this.$emit('submit-data', this.mailConfigData)
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
onChangeDriver() {
|
||||
this.$v.mailConfigData.mail_driver.$touch()
|
||||
this.$emit('on-change-driver', this.mailConfigData.mail_driver)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,335 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveEmailConfig">
|
||||
<div class="grid gap-6 grid-col-1 md:grid-cols-2">
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.driver')"
|
||||
:error="driverError"
|
||||
required
|
||||
>
|
||||
<sw-select
|
||||
v-model="mailConfigData.mail_driver"
|
||||
:invalid="$v.mailConfigData.mail_driver.$error"
|
||||
:options="mailDrivers"
|
||||
:searchable="true"
|
||||
:allow-empty="false"
|
||||
:show-labels="false"
|
||||
class="mt-2"
|
||||
@input="onChangeDriver"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.host')"
|
||||
:error="hostError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.mail_host.$error"
|
||||
v-model.trim="mailConfigData.mail_host"
|
||||
type="text"
|
||||
name="mail_host"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.mail_host.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.username')"
|
||||
:error="usernameError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.mail_username.$error"
|
||||
v-model.trim="mailConfigData.mail_username"
|
||||
type="text"
|
||||
name="db_name"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.mail_username.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.password')"
|
||||
:error="passwordError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.mail_password.$error"
|
||||
v-model.trim="mailConfigData.mail_password"
|
||||
:type="getInputType"
|
||||
name="password"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.mail_password.$touch()"
|
||||
>
|
||||
<template v-slot:rightIcon>
|
||||
<eye-off-icon
|
||||
v-if="isShowPassword"
|
||||
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
<eye-icon
|
||||
v-else
|
||||
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
</template>
|
||||
</sw-input>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.port')"
|
||||
:error="portError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.mail_port.$error"
|
||||
v-model.trim="mailConfigData.mail_port"
|
||||
type="text"
|
||||
name="mail_port"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.mail_port.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.encryption')"
|
||||
:error="encryptionError"
|
||||
required
|
||||
>
|
||||
<sw-select
|
||||
v-model.trim="mailConfigData.mail_encryption"
|
||||
:invalid="$v.mailConfigData.mail_encryption.$error"
|
||||
:options="encryptions"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.mail_encryption.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.from_mail')"
|
||||
:error="fromEmailError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.from_mail.$error"
|
||||
v-model.trim="mailConfigData.from_mail"
|
||||
type="text"
|
||||
name="from_mail"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.from_mail.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
|
||||
<sw-input-group
|
||||
:label="$t('settings.mail.from_name')"
|
||||
:error="fromNameError"
|
||||
required
|
||||
>
|
||||
<sw-input
|
||||
:invalid="$v.mailConfigData.from_name.$error"
|
||||
v-model.trim="mailConfigData.from_name"
|
||||
type="text"
|
||||
name="from_name"
|
||||
class="mt-2"
|
||||
@input="$v.mailConfigData.from_name.$touch()"
|
||||
/>
|
||||
</sw-input-group>
|
||||
</div>
|
||||
|
||||
<div class="flex my-10">
|
||||
<sw-button
|
||||
:disabled="loading"
|
||||
:loading="loading"
|
||||
type="submit"
|
||||
variant="primary"
|
||||
>
|
||||
<save-icon class="mr-2" />
|
||||
{{ $t('general.save') }}
|
||||
</sw-button>
|
||||
<slot />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const { required, email, numeric } = require('vuelidate/lib/validators')
|
||||
import { EyeIcon, EyeOffIcon } from '@vue-hero-icons/outline'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
configData: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
mailDrivers: {
|
||||
type: Array,
|
||||
require: true,
|
||||
default: Array,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
EyeIcon,
|
||||
EyeOffIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mailConfigData: {
|
||||
mail_driver: '',
|
||||
mail_host: '',
|
||||
mail_port: null,
|
||||
mail_username: '',
|
||||
mail_password: '',
|
||||
mail_encryption: 'tls',
|
||||
from_mail: '',
|
||||
from_name: '',
|
||||
},
|
||||
isShowPassword: false,
|
||||
encryptions: ['tls', 'ssl', 'starttls'],
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
mailConfigData: {
|
||||
mail_driver: {
|
||||
required,
|
||||
},
|
||||
mail_host: {
|
||||
required,
|
||||
},
|
||||
mail_port: {
|
||||
required,
|
||||
numeric,
|
||||
},
|
||||
mail_username: {
|
||||
required,
|
||||
},
|
||||
mail_password: {
|
||||
required,
|
||||
},
|
||||
mail_encryption: {
|
||||
required,
|
||||
},
|
||||
from_mail: {
|
||||
required,
|
||||
email,
|
||||
},
|
||||
from_name: {
|
||||
required,
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
driverError() {
|
||||
if (!this.$v.mailConfigData.mail_driver.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.mail_driver.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
hostError() {
|
||||
if (!this.$v.mailConfigData.mail_host.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.mail_host.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
usernameError() {
|
||||
if (!this.$v.mailConfigData.mail_username.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.mail_username.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
passwordError() {
|
||||
if (!this.$v.mailConfigData.mail_password.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.mail_password.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
portError() {
|
||||
if (!this.$v.mailConfigData.mail_port.$error) {
|
||||
return ''
|
||||
}
|
||||
if (!this.$v.mailConfigData.mail_port.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
if (!this.$v.mailConfigData.mail_port.numeric) {
|
||||
return this.$tc('validation.numbers_only')
|
||||
}
|
||||
},
|
||||
encryptionError() {
|
||||
if (!this.$v.mailConfigData.mail_encryption.$error) {
|
||||
return ''
|
||||
}
|
||||
if (!this.$v.mailConfigData.mail_encryption.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
fromEmailError() {
|
||||
if (!this.$v.mailConfigData.from_mail.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.from_mail.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.from_mail.email) {
|
||||
return this.$tc('validation.email_incorrect')
|
||||
}
|
||||
},
|
||||
fromNameError() {
|
||||
if (!this.$v.mailConfigData.from_name.$error) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (!this.$v.mailConfigData.from_name.required) {
|
||||
return this.$tc('validation.required')
|
||||
}
|
||||
},
|
||||
getInputType() {
|
||||
if (this.isShowPassword) {
|
||||
return 'text'
|
||||
}
|
||||
return 'password'
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
for (const key in this.mailConfigData) {
|
||||
if (this.configData.hasOwnProperty(key)) {
|
||||
this.mailConfigData[key] = this.configData[key]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async saveEmailConfig() {
|
||||
this.$v.mailConfigData.$touch()
|
||||
if (!this.$v.mailConfigData.$invalid) {
|
||||
this.$emit('submit-data', this.mailConfigData)
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
onChangeDriver() {
|
||||
this.$v.mailConfigData.mail_driver.$touch()
|
||||
this.$emit('on-change-driver', this.mailConfigData.mail_driver)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,163 +0,0 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveEmailConfig()">
|
||||
<div class="row">
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.driver') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-select
|
||||
v-model="mailConfigData.mail_driver"
|
||||
:invalid="$v.mailConfigData.mail_driver.$error"
|
||||
:options="mailDrivers"
|
||||
:searchable="true"
|
||||
:allow-empty="false"
|
||||
:show-labels="false"
|
||||
@input="onChangeDriver"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_driver.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_driver.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.host') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.mail_host.$error"
|
||||
v-model.trim="mailConfigData.mail_host"
|
||||
type="text"
|
||||
name="mail_host"
|
||||
@input="$v.mailConfigData.mail_host.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_host.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_host.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="row my-2">
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.from_mail') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.from_mail.$error"
|
||||
v-model.trim="mailConfigData.from_mail"
|
||||
type="text"
|
||||
name="from_mail"
|
||||
@input="$v.mailConfigData.from_mail.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.from_mail.$error">
|
||||
<span v-if="!$v.mailConfigData.from_mail.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
<span v-if="!$v.mailConfigData.from_mail.email" class="text-danger">
|
||||
{{ $tc('validation.email_incorrect') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.from_name') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.from_name.$error"
|
||||
v-model.trim="mailConfigData.from_name"
|
||||
type="text"
|
||||
name="name"
|
||||
@input="$v.mailConfigData.from_name.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.from_name.$error">
|
||||
<span v-if="!$v.mailConfigData.from_name.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<base-button
|
||||
:loading="loading"
|
||||
class="pull-right mt-4"
|
||||
icon="save"
|
||||
color="theme"
|
||||
type="submit"
|
||||
>
|
||||
{{ $t('general.save') }}
|
||||
</base-button>
|
||||
<slot/>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
<script>
|
||||
import MultiSelect from 'vue-multiselect'
|
||||
import { validationMixin } from 'vuelidate'
|
||||
const { required, email } = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MultiSelect
|
||||
},
|
||||
mixins: [validationMixin],
|
||||
props: {
|
||||
configData: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false
|
||||
},
|
||||
mailDrivers: {
|
||||
type: Array,
|
||||
require: true,
|
||||
default: Array
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
mailConfigData: {
|
||||
mail_driver: '',
|
||||
mail_host: '',
|
||||
from_mail: '',
|
||||
from_name: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
mailConfigData: {
|
||||
mail_driver: {
|
||||
required
|
||||
},
|
||||
from_mail: {
|
||||
required,
|
||||
email
|
||||
},
|
||||
from_name: {
|
||||
required
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
for (const key in this.mailConfigData) {
|
||||
if (this.configData.hasOwnProperty(key)) {
|
||||
this.mailConfigData[key] = this.configData[key]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async saveEmailConfig () {
|
||||
this.$v.mailConfigData.$touch()
|
||||
if (!this.$v.mailConfigData.$invalid) {
|
||||
this.$emit('submit-data', this.mailConfigData)
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
onChangeDriver () {
|
||||
this.$v.mailConfigData.mail_driver.$touch()
|
||||
this.$emit('on-change-driver', this.mailConfigData.mail_driver)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,212 +0,0 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveEmailConfig()">
|
||||
<div class="row">
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.driver') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-select
|
||||
v-model="mailConfigData.mail_driver"
|
||||
:invalid="$v.mailConfigData.mail_driver.$error"
|
||||
:options="mailDrivers"
|
||||
:allow-empty="false"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
@input="onChangeDriver"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_driver.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_driver.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.mailgun_domain') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.mail_mailgun_domain.$error"
|
||||
v-model.trim="mailConfigData.mail_mailgun_domain"
|
||||
type="text"
|
||||
name="mailgun_domain"
|
||||
@input="$v.mailConfigData.mail_mailgun_domain.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_mailgun_domain.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_mailgun_domain.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-2">
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.mailgun_secret') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.mail_mailgun_secret.$error"
|
||||
v-model.trim="mailConfigData.mail_mailgun_secret"
|
||||
type="password"
|
||||
name="mailgun_secret"
|
||||
show-password
|
||||
@input="$v.mailConfigData.mail_mailgun_secret.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_mailgun_secret.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_mailgun_secret.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.mailgun_endpoint') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.mail_mailgun_endpoint.$error"
|
||||
v-model.trim="mailConfigData.mail_mailgun_endpoint"
|
||||
type="text"
|
||||
name="mailgun_endpoint"
|
||||
@input="$v.mailConfigData.mail_mailgun_endpoint.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_mailgun_endpoint.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_mailgun_endpoint.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
<span v-if="!$v.mailConfigData.mail_mailgun_endpoint.numeric" class="text-danger">
|
||||
{{ $tc('validation.numbers_only') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-2">
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.from_mail') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.from_mail.$error"
|
||||
v-model.trim="mailConfigData.from_mail"
|
||||
type="text"
|
||||
name="from_mail"
|
||||
@input="$v.mailConfigData.from_mail.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.from_mail.$error">
|
||||
<span v-if="!$v.mailConfigData.from_mail.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
<span v-if="!$v.mailConfigData.from_mail.email" class="text-danger">
|
||||
{{ $tc('validation.email_incorrect') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.from_name') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.from_name.$error"
|
||||
v-model.trim="mailConfigData.from_name"
|
||||
type="text"
|
||||
name="from_name"
|
||||
@input="$v.mailConfigData.from_name.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.from_name.$error">
|
||||
<span v-if="!$v.mailConfigData.from_name.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<base-button
|
||||
:loading="loading"
|
||||
class="pull-right mt-4"
|
||||
icon="save"
|
||||
color="theme"
|
||||
type="submit"
|
||||
>
|
||||
{{ $t('general.save') }}
|
||||
</base-button>
|
||||
<slot/>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
<script>
|
||||
import MultiSelect from 'vue-multiselect'
|
||||
import { validationMixin } from 'vuelidate'
|
||||
const { required, email, numeric } = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MultiSelect
|
||||
},
|
||||
mixins: [validationMixin],
|
||||
props: {
|
||||
configData: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false
|
||||
},
|
||||
mailDrivers: {
|
||||
type: Array,
|
||||
require: true,
|
||||
default: Array
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
mailConfigData: {
|
||||
mail_driver: '',
|
||||
mail_mailgun_domain: '',
|
||||
mail_mailgun_secret: '',
|
||||
mail_mailgun_endpoint: '',
|
||||
from_mail: '',
|
||||
from_name: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
mailConfigData: {
|
||||
mail_driver: {
|
||||
required
|
||||
},
|
||||
mail_mailgun_domain: {
|
||||
required
|
||||
},
|
||||
mail_mailgun_endpoint: {
|
||||
required
|
||||
},
|
||||
mail_mailgun_secret: {
|
||||
required
|
||||
},
|
||||
from_mail: {
|
||||
required,
|
||||
email
|
||||
},
|
||||
from_name: {
|
||||
required
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
for (const key in this.mailConfigData) {
|
||||
if (this.configData.hasOwnProperty(key)) {
|
||||
this.mailConfigData[key] = this.configData[key]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async saveEmailConfig () {
|
||||
this.$v.mailConfigData.$touch()
|
||||
if (!this.$v.mailConfigData.$invalid) {
|
||||
this.$emit('submit-data', this.mailConfigData)
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
onChangeDriver () {
|
||||
this.$v.mailConfigData.mail_driver.$touch()
|
||||
this.$emit('on-change-driver', this.mailConfigData.mail_driver)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,257 +0,0 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveEmailConfig()">
|
||||
<div class="row">
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.driver') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-select
|
||||
v-model="mailConfigData.mail_driver"
|
||||
:invalid="$v.mailConfigData.mail_driver.$error"
|
||||
:options="mailDrivers"
|
||||
:allow-empty="false"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
@input="onChangeDriver"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_driver.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_driver.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.host') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.mail_host.$error"
|
||||
v-model.trim="mailConfigData.mail_host"
|
||||
type="text"
|
||||
name="mail_host"
|
||||
@input="$v.mailConfigData.mail_host.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_host.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_host.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-2">
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.port') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.mail_port.$error"
|
||||
v-model.trim="mailConfigData.mail_port"
|
||||
type="text"
|
||||
name="mail_port"
|
||||
@input="$v.mailConfigData.mail_port.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_port.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_port.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
<span v-if="!$v.mailConfigData.mail_port.numeric" class="text-danger">
|
||||
{{ $tc('validation.numbers_only') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.encryption') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-select
|
||||
v-model.trim="mailConfigData.mail_encryption"
|
||||
:invalid="$v.mailConfigData.mail_encryption.$error"
|
||||
:options="encryptions"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
@input="$v.mailConfigData.mail_encryption.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_encryption.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_encryption.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-2">
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.from_mail') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.from_mail.$error"
|
||||
v-model.trim="mailConfigData.from_mail"
|
||||
type="text"
|
||||
name="from_mail"
|
||||
@input="$v.mailConfigData.from_mail.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.from_mail.$error">
|
||||
<span v-if="!$v.mailConfigData.from_mail.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
<span v-if="!$v.mailConfigData.from_mail.email" class="text-danger">
|
||||
{{ $tc('validation.email_incorrect') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.from_name') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.from_name.$error"
|
||||
v-model.trim="mailConfigData.from_name"
|
||||
type="text"
|
||||
name="name"
|
||||
@input="$v.mailConfigData.from_name.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.from_name.$error">
|
||||
<span v-if="!$v.mailConfigData.from_name.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-2">
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.ses_key') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.mail_ses_key.$error"
|
||||
v-model.trim="mailConfigData.mail_ses_key"
|
||||
type="text"
|
||||
name="mail_ses_key"
|
||||
@input="$v.mailConfigData.mail_ses_key.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_ses_key.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_ses_key.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.ses_secret') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.mail_ses_secret.$error"
|
||||
v-model.trim="mailConfigData.mail_ses_secret"
|
||||
type="password"
|
||||
name="mail_ses_secret"
|
||||
show-password
|
||||
@input="$v.mailConfigData.mail_ses_secret.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_ses_secret.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_ses_secret.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<base-button
|
||||
:loading="loading"
|
||||
class="pull-right mt-4"
|
||||
icon="save"
|
||||
color="theme"
|
||||
type="submit"
|
||||
>
|
||||
{{ $t('general.save') }}
|
||||
</base-button>
|
||||
<slot/>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
<script>
|
||||
import MultiSelect from 'vue-multiselect'
|
||||
import { validationMixin } from 'vuelidate'
|
||||
const { required, email, numeric } = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MultiSelect
|
||||
},
|
||||
mixins: [validationMixin],
|
||||
props: {
|
||||
configData: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false
|
||||
},
|
||||
mailDrivers: {
|
||||
type: Array,
|
||||
require: true,
|
||||
default: Array
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
mailConfigData: {
|
||||
mail_driver: '',
|
||||
mail_host: '',
|
||||
mail_port: null,
|
||||
mail_ses_key: '',
|
||||
mail_ses_secret: '',
|
||||
mail_encryption: 'tls',
|
||||
from_mail: '',
|
||||
from_name: ''
|
||||
},
|
||||
encryptions: ['tls', 'ssl', 'starttls']
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
mailConfigData: {
|
||||
mail_driver: {
|
||||
required
|
||||
},
|
||||
mail_host: {
|
||||
required
|
||||
},
|
||||
mail_port: {
|
||||
required,
|
||||
numeric
|
||||
},
|
||||
mail_ses_key: {
|
||||
required
|
||||
},
|
||||
mail_ses_secret: {
|
||||
required
|
||||
},
|
||||
mail_encryption: {
|
||||
required
|
||||
},
|
||||
from_mail: {
|
||||
required,
|
||||
email
|
||||
},
|
||||
from_name: {
|
||||
required
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
for (const key in this.mailConfigData) {
|
||||
if (this.configData.hasOwnProperty(key)) {
|
||||
this.mailConfigData[key] = this.configData[key]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async saveEmailConfig () {
|
||||
this.$v.mailConfigData.$touch()
|
||||
if (!this.$v.mailConfigData.$invalid) {
|
||||
this.$emit('submit-data', this.mailConfigData)
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
onChangeDriver () {
|
||||
this.$v.mailConfigData.mail_driver.$touch()
|
||||
this.$emit('on-change-driver', this.mailConfigData.mail_driver)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,257 +0,0 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveEmailConfig()">
|
||||
<div class="row">
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.driver') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-select
|
||||
v-model="mailConfigData.mail_driver"
|
||||
:invalid="$v.mailConfigData.mail_driver.$error"
|
||||
:options="mailDrivers"
|
||||
:searchable="true"
|
||||
:allow-empty="false"
|
||||
:show-labels="false"
|
||||
@input="onChangeDriver"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_driver.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_driver.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.host') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.mail_host.$error"
|
||||
v-model.trim="mailConfigData.mail_host"
|
||||
type="text"
|
||||
name="mail_host"
|
||||
@input="$v.mailConfigData.mail_host.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_host.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_host.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-2">
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.username') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.mail_username.$error"
|
||||
v-model.trim="mailConfigData.mail_username"
|
||||
type="text"
|
||||
name="db_name"
|
||||
@input="$v.mailConfigData.mail_username.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_username.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_username.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.password') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.mail_password.$error"
|
||||
v-model.trim="mailConfigData.mail_password"
|
||||
type="password"
|
||||
name="name"
|
||||
show-password
|
||||
@input="$v.mailConfigData.mail_password.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_password.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_password.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-2">
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.port') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.mail_port.$error"
|
||||
v-model.trim="mailConfigData.mail_port"
|
||||
type="text"
|
||||
name="mail_port"
|
||||
@input="$v.mailConfigData.mail_port.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_port.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_port.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
<span v-if="!$v.mailConfigData.mail_port.numeric" class="text-danger">
|
||||
{{ $tc('validation.numbers_only') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.encryption') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-select
|
||||
v-model.trim="mailConfigData.mail_encryption"
|
||||
:invalid="$v.mailConfigData.mail_encryption.$error"
|
||||
:options="encryptions"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
@input="$v.mailConfigData.mail_encryption.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.mail_encryption.$error">
|
||||
<span v-if="!$v.mailConfigData.mail_encryption.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-2">
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.from_mail') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.from_mail.$error"
|
||||
v-model.trim="mailConfigData.from_mail"
|
||||
type="text"
|
||||
name="from_mail"
|
||||
@input="$v.mailConfigData.from_mail.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.from_mail.$error">
|
||||
<span v-if="!$v.mailConfigData.from_mail.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
<span v-if="!$v.mailConfigData.from_mail.email" class="text-danger">
|
||||
{{ $tc('validation.email_incorrect') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 my-2">
|
||||
<label class="form-label">{{ $t('settings.mail.from_name') }}</label>
|
||||
<span class="text-danger"> *</span>
|
||||
<base-input
|
||||
:invalid="$v.mailConfigData.from_name.$error"
|
||||
v-model.trim="mailConfigData.from_name"
|
||||
type="text"
|
||||
name="from_name"
|
||||
@input="$v.mailConfigData.from_name.$touch()"
|
||||
/>
|
||||
<div v-if="$v.mailConfigData.from_name.$error">
|
||||
<span v-if="!$v.mailConfigData.from_name.required" class="text-danger">
|
||||
{{ $tc('validation.required') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<base-button
|
||||
:loading="loading"
|
||||
class="pull-right mt-4"
|
||||
icon="save"
|
||||
color="theme"
|
||||
type="submit"
|
||||
>
|
||||
{{ $t('general.save') }}
|
||||
</base-button>
|
||||
<slot/>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
<script>
|
||||
import MultiSelect from 'vue-multiselect'
|
||||
import { validationMixin } from 'vuelidate'
|
||||
const { required, email, numeric } = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MultiSelect
|
||||
},
|
||||
mixins: [validationMixin],
|
||||
props: {
|
||||
configData: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false
|
||||
},
|
||||
mailDrivers: {
|
||||
type: Array,
|
||||
require: true,
|
||||
default: Array
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
mailConfigData: {
|
||||
mail_driver: '',
|
||||
mail_host: '',
|
||||
mail_port: null,
|
||||
mail_username: '',
|
||||
mail_password: '',
|
||||
mail_encryption: 'tls',
|
||||
from_mail: '',
|
||||
from_name: ''
|
||||
},
|
||||
encryptions: ['tls', 'ssl', 'starttls']
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
mailConfigData: {
|
||||
mail_driver: {
|
||||
required
|
||||
},
|
||||
mail_host: {
|
||||
required
|
||||
},
|
||||
mail_port: {
|
||||
required,
|
||||
numeric
|
||||
},
|
||||
mail_username: {
|
||||
required
|
||||
},
|
||||
mail_password: {
|
||||
required
|
||||
},
|
||||
mail_encryption: {
|
||||
required
|
||||
},
|
||||
from_mail: {
|
||||
required,
|
||||
email
|
||||
},
|
||||
from_name: {
|
||||
required
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
for (const key in this.mailConfigData) {
|
||||
if (this.configData.hasOwnProperty(key)) {
|
||||
this.mailConfigData[key] = this.configData[key]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async saveEmailConfig () {
|
||||
this.$v.mailConfigData.$touch()
|
||||
if (!this.$v.mailConfigData.$invalid) {
|
||||
this.$emit('submit-data', this.mailConfigData)
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
onChangeDriver () {
|
||||
this.$v.mailConfigData.mail_driver.$touch()
|
||||
this.$emit('on-change-driver', this.mailConfigData.mail_driver)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user