mirror of
				https://github.com/crater-invoice/crater.git
				synced 2025-10-31 05:31:10 -04: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