mirror of
				https://github.com/crater-invoice/crater.git
				synced 2025-10-31 05:31:10 -04:00 
			
		
		
		
	init crater
This commit is contained in:
		
							
								
								
									
										99
									
								
								resources/assets/js/views/auth/ForgotPassword.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								resources/assets/js/views/auth/ForgotPassword.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,99 @@ | ||||
| <template> | ||||
|   <form | ||||
|     id="loginForm" | ||||
|     @submit.prevent="validateBeforeSubmit" | ||||
|   > | ||||
|  | ||||
|     <div :class="{'form-group' : true }"> | ||||
|       <base-input | ||||
|         :invalid="$v.formData.email.$error" | ||||
|         v-model.lazy="formData.email" | ||||
|         :disabled="isSent" | ||||
|         :placeholder="$t('login.enter_email')" | ||||
|         focus | ||||
|         name="email" | ||||
|         @blur="$v.formData.email.$touch()" | ||||
|       /> | ||||
|       <div v-if="$v.formData.email.$error"> | ||||
|         <span v-if="!$v.formData.email.required" class="help-block text-danger"> | ||||
|           {{ $t('validation.required') }} | ||||
|         </span> | ||||
|         <span v-if="!$v.formData.email.email" class="help-block text-danger"> | ||||
|           {{ $t('validation.email_incorrect') }} | ||||
|         </span> | ||||
|       </div> | ||||
|     </div> | ||||
|     <base-button v-if="!isSent" :loading="isLoading" :disabled="isLoading" type="submit" color="theme"> | ||||
|       {{ $t('validation.send_reset_link') }} | ||||
|     </base-button> | ||||
|     <base-button v-else :loading="isLoading" :disabled="isLoading" color="theme" type="submit"> | ||||
|       {{ $t('validation.not_yet') }} | ||||
|     </base-button> | ||||
|  | ||||
|     <div class="other-actions mb-4"> | ||||
|       <router-link to="/login"> | ||||
|         {{ $t('general.back_to_login') }} | ||||
|       </router-link> | ||||
|     </div> | ||||
|   </form> | ||||
| </template> | ||||
|  | ||||
| <script type="text/babel"> | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import { async } from 'q' | ||||
| const { required, email } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       formData: { | ||||
|         email: '' | ||||
|       }, | ||||
|       isSent: false, | ||||
|       isLoading: false, | ||||
|       isRegisteredUser: false | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     formData: { | ||||
|       email: { | ||||
|         email, | ||||
|         required | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|  | ||||
|     async validateBeforeSubmit (e) { | ||||
|       this.$v.formData.$touch() | ||||
|  | ||||
|       if (await this.checkMail() === false) { | ||||
|         toastr['error'](this.$t('validation.email_does_not_exist')) | ||||
|         return | ||||
|       } | ||||
|       if (!this.$v.formData.$invalid) { | ||||
|         try { | ||||
|           this.isLoading = true | ||||
|           let res = await axios.post('/api/auth/password/email', this.formData) | ||||
|  | ||||
|           if (res.data) { | ||||
|             toastr['success']('Mail sent successfuly!', 'Success') | ||||
|           } | ||||
|  | ||||
|           this.isSent = true | ||||
|           this.isLoading = false | ||||
|         } catch (err) { | ||||
|           if (err.response && err.response.status === 403) { | ||||
|             toastr['error'](err.response.data, 'Error') | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     async checkMail () { | ||||
|       let response = await window.axios.post('/api/is-registered', this.formData) | ||||
|       return response.data | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										119
									
								
								resources/assets/js/views/auth/Login.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								resources/assets/js/views/auth/Login.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,119 @@ | ||||
| <template> | ||||
|   <form | ||||
|     id="loginForm" | ||||
|     @submit.prevent="validateBeforeSubmit" | ||||
|   > | ||||
|     <div :class="{'form-group' : true }"> | ||||
|       <p class="input-label">{{ $t('login.email') }} <span class="text-danger"> * </span></p> | ||||
|       <base-input | ||||
|         :invalid="$v.loginData.email.$error" | ||||
|         v-model="loginData.email" | ||||
|         :placeholder="$t(login.login_placeholder)" | ||||
|         focus | ||||
|         type="email" | ||||
|         name="email" | ||||
|       /> | ||||
|       <div v-if="$v.loginData.email.$error"> | ||||
|         <span v-if="!$v.loginData.email.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|         <span v-if="!$v.loginData.email.email" class="text-danger"> {{ $tc('validation.email_incorrect') }} </span> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="form-group"> | ||||
|       <p class="input-label">{{ $t('login.password') }} <span class="text-danger"> * </span></p> | ||||
|       <base-input | ||||
|         v-model="loginData.password" | ||||
|         :invalid="$v.loginData.password.$error" | ||||
|         type="password" | ||||
|         name="password" | ||||
|       /> | ||||
|       <div v-if="$v.loginData.email.$error"> | ||||
|         <span v-if="!$v.loginData.password.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|         <span v-if="!$v.loginData.password.minLength" class="text-danger"> {{ $tc('validation.password_min_length', $v.loginData.password.$params.minLength.min, {count: $v.loginData.password.$params.minLength.min}) }} </span> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="other-actions row"> | ||||
|       <div class="col-sm-12 text-sm-left mb-4"> | ||||
|         <router-link to="forgot-password" class="forgot-link"> | ||||
|           {{ $t('login.forgot_password') }} | ||||
|         </router-link> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <base-button type="submit" color="theme">{{ $t('login.login') }}</base-button> | ||||
|  | ||||
|     <!-- <div class="social-links"> | ||||
|  | ||||
|       <span class="link-text">{{ $t('login.or_signIn_with') }}</span> | ||||
|  | ||||
|       <div class="social-logo"> | ||||
|         <icon-facebook class="icon"/> | ||||
|         <icon-twitter class="icon"/> | ||||
|         <icon-google class="icon"/> | ||||
|       </div> | ||||
|  | ||||
|     </div> --> | ||||
|  | ||||
|   </form> | ||||
| </template> | ||||
|  | ||||
| <script type="text/babel"> | ||||
| import { mapActions } from 'vuex' | ||||
|  | ||||
| import IconFacebook from '../../components/icon/facebook' | ||||
| import IconTwitter from '../../components/icon/twitter' | ||||
| import IconGoogle from '../../components/icon/google' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| const { required, email, minLength } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|  | ||||
|   components: { | ||||
|     IconFacebook, | ||||
|     IconTwitter, | ||||
|     IconGoogle | ||||
|   }, | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       loginData: { | ||||
|         email: '', | ||||
|         password: '', | ||||
|         remember: '' | ||||
|       }, | ||||
|       submitted: false | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     loginData: { | ||||
|       email: { | ||||
|         required, | ||||
|         email | ||||
|       }, | ||||
|       password: { | ||||
|         required, | ||||
|         minLength: minLength(8) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('auth', [ | ||||
|       'login' | ||||
|     ]), | ||||
|     async validateBeforeSubmit () { | ||||
|       this.$v.loginData.$touch() | ||||
|       if (this.$v.$invalid) { | ||||
|         return true | ||||
|       } | ||||
|  | ||||
|       this.isLoading = true | ||||
|  | ||||
|       this.login(this.loginData).then((res) => { | ||||
|         this.$router.push('/admin/dashboard') | ||||
|         this.isLoading = false | ||||
|       }).catch(() => { | ||||
|         this.isLoading = false | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										57
									
								
								resources/assets/js/views/auth/Register.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								resources/assets/js/views/auth/Register.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | ||||
| <template> | ||||
|   <form | ||||
|     id="registerForm" | ||||
|     action="" | ||||
|     method="post" | ||||
|   > | ||||
|     <!-- {{ csrf_field() }} --> | ||||
|     <div class="form-group"> | ||||
|       <input | ||||
|         :placeholder="$t('login.enter_email')" | ||||
|         type="email" | ||||
|         class="form-control form-control-danger" | ||||
|         name="email" | ||||
|       > | ||||
|     </div> | ||||
|     <div class="form-group"> | ||||
|       <input | ||||
|         id="password" | ||||
|         type="password" | ||||
|         class="form-control form-control-danger" | ||||
|         placeholder="Enter Password" | ||||
|         name="password" | ||||
|       > | ||||
|     </div> | ||||
|     <div class="form-group"> | ||||
|       <input | ||||
|         type="password" | ||||
|         class="form-control form-control-danger" | ||||
|         placeholder="Retype Password" | ||||
|         name="password_confirmation" | ||||
|       > | ||||
|     </div> | ||||
|     <base-button class="btn btn-login btn-full">{{ $t('login.register') }}</base-button> | ||||
|   </form> | ||||
| </template> | ||||
| <script type="text/babel"> | ||||
| export default { | ||||
|   data () { | ||||
|     return { | ||||
|       name: '', | ||||
|       email: '', | ||||
|       password: '', | ||||
|       password_confirmation: '' | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     validateBeforeSubmit (e) { | ||||
|       this.$validator.validateAll().then((result) => { | ||||
|         if (result) { | ||||
|           // eslint-disable-next-line | ||||
|           alert('Form Submitted!') | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										124
									
								
								resources/assets/js/views/auth/ResetPassword.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								resources/assets/js/views/auth/ResetPassword.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,124 @@ | ||||
| <template> | ||||
|   <form | ||||
|     id="loginForm" | ||||
|     @submit.prevent="validateBeforeSubmit" | ||||
|   > | ||||
|     <div class="form-group"> | ||||
|       <base-input | ||||
|         v-model.trim="formData.email" | ||||
|         :invalid="$v.formData.email.$error" | ||||
|         :placeholder="$t('login.enter_email')" | ||||
|         type="email" | ||||
|         name="email" | ||||
|         @input="$v.formData.email.$touch()" | ||||
|       /> | ||||
|       <div v-if="$v.formData.email.$error"> | ||||
|         <span v-if="!$v.formData.email.required" class="help-block text-danger"> | ||||
|           {{ $t('validation.required') }} | ||||
|         </span> | ||||
|         <span v-if="!$v.formData.email.email" class="help-block text-danger"> | ||||
|           {{ $t('validation.email_incorrect') }} | ||||
|         </span> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="form-group"> | ||||
|       <base-input | ||||
|         id="password" | ||||
|         v-model.trim="formData.password" | ||||
|         :invalid="$v.formData.password.$error" | ||||
|         :placeholder="$t('login.enter_password')" | ||||
|         type="password" | ||||
|         name="password" | ||||
|         @input="$v.formData.password.$touch()" | ||||
|       /> | ||||
|       <div v-if="$v.formData.password.$error"> | ||||
|         <span v-if="!$v.formData.password.required" class="help-block text-danger"> | ||||
|           {{ $t('validation.required') }} | ||||
|         </span> | ||||
|         <span v-if="!$v.formData.password.minLength" class="help-block text-danger"> | ||||
|           {{ $tc('validation.password_length', $v.formData.password.minLength.min, { count: $v.formData.password.$params.minLength.min }) }} | ||||
|         </span> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="form-group"> | ||||
|       <base-input | ||||
|         v-model.trim="formData.password_confirmation" | ||||
|         :invalid="$v.formData.password_confirmation.$error" | ||||
|         :placeholder="$t('login.retype_password')" | ||||
|         type="password" | ||||
|         name="password_confirmation" | ||||
|         @input="$v.formData.password_confirmation.$touch()" | ||||
|       /> | ||||
|       <div v-if="$v.formData.password_confirmation.$error"> | ||||
|         <span v-if="!$v.formData.password_confirmation.sameAsPassword" class="help-block text-danger"> | ||||
|           {{ $t('validation.password_incorrect') }} | ||||
|         </span> | ||||
|       </div> | ||||
|     </div> | ||||
|     <base-button :loading="isLoading" type="submit" color="theme"> | ||||
|       {{ $t('login.reset_password') }} | ||||
|     </base-button> | ||||
|   </form> | ||||
| </template> | ||||
|  | ||||
| <script type="text/babel"> | ||||
| import { validationMixin } from 'vuelidate' | ||||
| const { required, email, sameAs, minLength } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       formData: { | ||||
|         email: '', | ||||
|         password: '', | ||||
|         password_confirmation: '' | ||||
|       }, | ||||
|       isLoading: false | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     formData: { | ||||
|       email: { | ||||
|         required, | ||||
|         email | ||||
|       }, | ||||
|       password: { | ||||
|         required, | ||||
|         minLength: minLength(8) | ||||
|       }, | ||||
|       password_confirmation: { | ||||
|         sameAsPassword: sameAs('password') | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     async validateBeforeSubmit (e) { | ||||
|       this.$v.formData.$touch() | ||||
|  | ||||
|       if (!this.$v.formData.$invalid) { | ||||
|         try { | ||||
|           let data = { | ||||
|             email: this.formData.email, | ||||
|             password: this.formData.password, | ||||
|             password_confirmation: this.formData.password_confirmation, | ||||
|             token: this.$route.params.token | ||||
|           } | ||||
|           this.isLoading = true | ||||
|           let res = await axios.post('/api/auth/reset/password', data) | ||||
|           this.isLoading = false | ||||
|           if (res.data) { | ||||
|             toastr['success'](this.$t('login.password_reset_successfully'), 'Success') | ||||
|             this.$router.push('/login') | ||||
|           } | ||||
|         } catch (err) { | ||||
|           if (err.response && err.response.status === 403) { | ||||
|             toastr['error'](err.response.data, this.$t('validation.email_incorrect')) | ||||
|             this.isLoading = false | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										96
									
								
								resources/assets/js/views/categories/Create.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								resources/assets/js/views/categories/Create.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,96 @@ | ||||
| <template> | ||||
|   <div class="main-content categoriescreate"> | ||||
|     <div class="page-header"> | ||||
|       <h3 class="page-title">{{ $t('categories.new_category') }}</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/expenses">{{ $tc('categorie.category',2) }}</router-link></li> | ||||
|         <li class="breadcrumb-item"><a href="#">{{ $t('categories.new_category') }}</a></li> | ||||
|       </ol> | ||||
|       <div class="page-actions"> | ||||
|         <router-link slot="item-title" to="/admin/expenses"> | ||||
|           <base-button class="btn btn-primary" color="theme"> | ||||
|             <font-awesome-icon icon="backward" class="mr-2"/> {{ $t('general.go_back') }} | ||||
|           </base-button> | ||||
|         </router-link> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="row"> | ||||
|       <div class="col-sm-6"> | ||||
|         <div class="card"> | ||||
|           <form action="" @submit.prevent="submitCategoryData"> | ||||
|             <div class="card-body"> | ||||
|               <div class="form-group"> | ||||
|                 <label class="control-label">{{ $t('expenses.categories.title') }}</label><span class="text-danger"> *</span> | ||||
|                 <base-input | ||||
|                   :invalid="$v.formData.name.$error" | ||||
|                   v-model.trim="formData.name" | ||||
|                   type="text" | ||||
|                   name="name" | ||||
|                   @input="$v.formData.name.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.formData.name.$error"> | ||||
|                   <span v-if="!$v.formData.name.required" class="text-danger">{{ $t('validation.required') }}</span> | ||||
|                   <span v-if="!$v.formData.name.minLength" class="text-danger"> {{ $tc('validation.name_min_length', $v.formData.name.$params.minLength.min, {count: $v.formData.name.$params.minLength.min}) }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label for="description">{{ $t('expenses.categories.description') }}</label> | ||||
|                 <base-text-area v-model="formData.description" rows="5" name="description" /> | ||||
|               </div> | ||||
|               <base-button icon="save" type="submit" color="theme"> | ||||
|                 {{ $t('general.save') }} | ||||
|               </base-button> | ||||
|             </div> | ||||
|           </form> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import { mapActions } from 'vuex' | ||||
| const { required, minLength } = require('vuelidate/lib/validators') | ||||
| export default { | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       formData: { | ||||
|         name: null, | ||||
|         description: null | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     formData: { | ||||
|       name: { | ||||
|         required, | ||||
|         minLength: minLength(3) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('category', [ | ||||
|       'loadData', | ||||
|       'addCategory' | ||||
|     ]), | ||||
|     async submitCategoryData () { | ||||
|       this.$v.formData.$touch() | ||||
|       if (this.$v.$invalid) { | ||||
|         return true | ||||
|       } | ||||
|       let response = await this.addCategory({ ...this.formData }) | ||||
|       if (response.data.success) { | ||||
|         window.toastr['success'](response.data.success) | ||||
|         this.$router.push('/admin/expenses') | ||||
|         return true | ||||
|       } | ||||
|       window.toastr['error'](response.data.error) | ||||
|       return true | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										114
									
								
								resources/assets/js/views/categories/Edit.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								resources/assets/js/views/categories/Edit.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,114 @@ | ||||
| <template> | ||||
|   <div class="main-content categoriescreate"> | ||||
|     <div class="page-header"> | ||||
|       <h3 v-if="!isEdit" class="page-title">{{ $t('categories.add_category') }}</h3> | ||||
|       <h3 v-else class="page-title">{{ $t('navigation.edit') }} {{ $tc('navigation.category',1) }}</h3> | ||||
|       <ol class="breadcrumb"> | ||||
|         <li class="breadcrumb-item"><router-link slot="item-title" to="/admin/dashboard">{{ $t('navigation.home') }}</router-link></li> | ||||
|         <li class="breadcrumb-item"><router-link slot="item-title" to="/admin/expenses">{{ $tc('navigation.category',2) }}</router-link></li> | ||||
|         <li v-if="!isEdit" class="breadcrumb-item"><a href="#">{{ $t('expenses.categories.add_category') }} {{ $tc('navigation.category', 1) }}</a></li> | ||||
|         <li v-else class="breadcrumb-item"><a href="#">{{ $t('navigation.edit') }} {{ $tc('navigation.category', 1) }}</a></li> | ||||
|       </ol> | ||||
|       <div class="page-actions"> | ||||
|         <router-link slot="item-title" to="/admin/expenses"> | ||||
|           <base-button icon="backward" color="theme"> | ||||
|             {{ $t('navigation.go_back') }} | ||||
|           </base-button> | ||||
|         </router-link> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="row"> | ||||
|       <div class="col-sm-6"> | ||||
|         <div class="card"> | ||||
|           <form action="" @submit.prevent="submitCategoryData"> | ||||
|             <div class="card-body"> | ||||
|               <div class="form-group"> | ||||
|                 <label class="control-label">{{ $t('expenses.categories.title') }}</label><span class="text-danger"> *</span> | ||||
|                 <input | ||||
|                   :class="{ error: $v.formData.name.$error }" | ||||
|                   v-model.trim="formData.name" | ||||
|                   type="text" | ||||
|                   name="name" | ||||
|                   class="form-control" | ||||
|                   @input="$v.formData.name.$touch()" | ||||
|                 > | ||||
|                 <div v-if="$v.formData.name.$error"> | ||||
|                   <span v-if="!$v.formData.name.required" class="text-danger">{{ $t('validation.required') }}</span> | ||||
|                   <span v-if="!$v.formData.name.minLength" class="text-danger"> {{ $tc('validation.name_min_length', $v.formData.name.$params.minLength.min, {count: $v.formData.name.$params.minLength.min}) }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label for="description">{{ $t('expenses.categories.description') }}</label> | ||||
|                 <textarea v-model="formData.description" class="form-control" rows="5" name="description" /> | ||||
|               </div> | ||||
|               <base-button icon="save" color="theme" type="submit"> | ||||
|                 {{ $t('navigation.save') }} | ||||
|               </base-button> | ||||
|             </div> | ||||
|           </form> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import { mapActions } from 'vuex' | ||||
| const { required, minLength } = require('vuelidate/lib/validators') | ||||
| export default { | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       formData: { | ||||
|         name: null, | ||||
|         description: null | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     isEdit () { | ||||
|       if (this.$route.name === 'categoryedit') { | ||||
|         return true | ||||
|       } | ||||
|       return false | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     formData: { | ||||
|       name: { | ||||
|         required, | ||||
|         minLength: minLength(3) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     this.fetchInitialData() | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('category', [ | ||||
|       'loadData', | ||||
|       'addCategory', | ||||
|       'editCategory' | ||||
|     ]), | ||||
|     async fetchInitialData () { | ||||
|       let response = await this.loadData(this.$route.params.id) | ||||
|       this.formData.name = response.data.category.name | ||||
|       this.formData.description = response.data.category.description | ||||
|     }, | ||||
|     async submitCategoryData () { | ||||
|       this.$v.formData.$touch() | ||||
|       if (this.$v.$invalid) { | ||||
|         return true | ||||
|       } | ||||
|       let response = await this.editCategory({ id: this.$route.params.id, editData: { ...this.formData } }) | ||||
|       if (response.data.success) { | ||||
|         window.toastr['success'](response.data.success) | ||||
|         this.$router.push('/admin/expenses') | ||||
|         return true | ||||
|       } | ||||
|       window.toastr['error'](response.data.error) | ||||
|       return true | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										120
									
								
								resources/assets/js/views/components/ImageBox.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								resources/assets/js/views/components/ImageBox.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,120 @@ | ||||
| <template> | ||||
|   <div class="imgbox"> | ||||
|     <vue-dropzone | ||||
|       id="dropzone" | ||||
|       ref="myVueDropzone" | ||||
|       :include-styling="true" | ||||
|       :options="dropzoneOptions" | ||||
|       @vdropzone-sending="sendingEvent" | ||||
|       @vdropzone-success="successEvent" | ||||
|       @vdropzone-max-files-exceeded="maximum" | ||||
|       @vdropzone-file-added="getCustomeFile" | ||||
|       @vdropzone-removed-file="removeFile" | ||||
|     /> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import vue2Dropzone from 'vue2-dropzone' | ||||
| import 'vue2-dropzone/dist/vue2Dropzone.min.css' | ||||
| export default { | ||||
|   components: { | ||||
|     vueDropzone: vue2Dropzone | ||||
|   }, | ||||
|   props: { | ||||
|     additionaldata: { | ||||
|       type: Array, | ||||
|       default () { | ||||
|         return [] | ||||
|       } | ||||
|     }, | ||||
|     url: { | ||||
|       type: String, | ||||
|       default () { | ||||
|         return '' | ||||
|       } | ||||
|     }, | ||||
|     router: { | ||||
|       type: Object, | ||||
|       default: null | ||||
|     }, | ||||
|     paramname: { | ||||
|       type: String, | ||||
|       default () { | ||||
|         return '' | ||||
|       } | ||||
|     }, | ||||
|     acceptedfiles: { | ||||
|       type: String, | ||||
|       default () { | ||||
|         return '' | ||||
|       } | ||||
|     }, | ||||
|     dictdefaultmessage: { | ||||
|       type: String, | ||||
|       default () { | ||||
|         return '' | ||||
|       } | ||||
|     }, | ||||
|     autoprocessqueue: { | ||||
|       type: Boolean, | ||||
|       default: true | ||||
|     }, | ||||
|     method: { | ||||
|       type: String, | ||||
|       default: 'POST' | ||||
|     } | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       dropzoneOptions: { | ||||
|         autoProcessQueue: this.autoprocessqueue, | ||||
|         url: this.url, | ||||
|         thumbnailWidth: 110, | ||||
|         maxFiles: 1, | ||||
|         paramName: this.paramname, | ||||
|         acceptedFiles: this.acceptedfiles, | ||||
|         uploadMultiple: false, | ||||
|         dictDefaultMessage: '<font-awesome-icon icon="trash"/> ' + this.dictdefaultmessage, | ||||
|         dictInvalidFileType: 'This file type is not supported.', | ||||
|         dictFileTooBig: 'File size too Big', | ||||
|         addRemoveLinks: true, | ||||
|         method: this.method, | ||||
|         headers: { 'Authorization': `Bearer ${window.Ls.get('auth.token')}`, 'Company': `${window.Ls.get('selectedCompany')}` } | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     url (newURL) { | ||||
|       this.$refs.myVueDropzone.options.url = newURL | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     window.hub.$on('sendFile', this.customeSend) | ||||
|   }, | ||||
|   methods: { | ||||
|     sendingEvent (file, xhr, formData) { | ||||
|       var i | ||||
|       for (i = 0; i < this.additionaldata.length; i++) { | ||||
|         for (var key in this.additionaldata[i]) { | ||||
|           formData.append(key, this.additionaldata[i][key]) | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     successEvent (file, response) { | ||||
|       // window.toastr['success'](response.success) | ||||
|     }, | ||||
|     maximum (file) { | ||||
|       this.$refs.myVueDropzone.removeFile(file) | ||||
|     }, | ||||
|     getCustomeFile (file) { | ||||
|       this.$emit('takefile', true) | ||||
|     }, | ||||
|     removeFile (file, error, xhr) { | ||||
|       this.$emit('takefile', false) | ||||
|     }, | ||||
|     customeSend () { | ||||
|       this.$refs.myVueDropzone.processQueue() | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										71
									
								
								resources/assets/js/views/components/ImageRadio.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								resources/assets/js/views/components/ImageRadio.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | ||||
| <template> | ||||
|   <div class="form-group image-radio"> | ||||
|     <div | ||||
|       v-for="(pdfStyleList, index) in pdfStyleLists" | ||||
|       :key="index" | ||||
|       class="radio" | ||||
|     > | ||||
|       <label :for="pdfStyleList.val"> | ||||
|         <input | ||||
|           v-model="checkedID" | ||||
|           :value="pdfStyleList.val" | ||||
|           :id="pdfStyleList.val" | ||||
|           :checked="pdfStyleList.val == checkedID" | ||||
|           type="radio" | ||||
|           name="pdfSet" | ||||
|           class="hidden" | ||||
|         > | ||||
|         <img | ||||
|           :src="srcMaker(pdfStyleList.src)" | ||||
|           alt="No Image" | ||||
|           class="special-img" | ||||
|         > | ||||
|       </label> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default{ | ||||
|   props: { | ||||
|     currentPDF: { | ||||
|       type: String, | ||||
|       default: '' | ||||
|     } | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       pdfStyleLists: [ | ||||
|         {src: 'assets/img/PDF/Invoice1.png', val: '1'}, | ||||
|         {src: 'assets/img/PDF/Invoice2.png', val: '2'}, | ||||
|         {src: 'assets/img/PDF/Invoice3.png', val: '3'}, | ||||
|         {src: 'assets/img/PDF/Invoice4.png', val: '4'} | ||||
|       ], | ||||
|       checkedID: '' | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     checkedID (newID) { | ||||
|       if (newID !== null) { | ||||
|         this.$emit('selectedPDF', newID) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     setTimeout(() => { | ||||
|       if (this.currentPDF === '') { | ||||
|         this.checkedID = null | ||||
|       } else { | ||||
|         this.checkedID = this.currentPDF | ||||
|       } | ||||
|     }, 1000) | ||||
|   }, | ||||
|   methods: { | ||||
|     srcMaker (file) { | ||||
|       var url = '/' | ||||
|       var full = url + '' + file | ||||
|       return full | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										129
									
								
								resources/assets/js/views/components/SettingListBox.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								resources/assets/js/views/components/SettingListBox.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,129 @@ | ||||
| <template> | ||||
|   <div class="setting-list-box"> | ||||
|     <div id="myApp list-box-container"> | ||||
|       <!-- <v-select | ||||
|         :value.sync="selected" | ||||
|         :options="list" | ||||
|         :on-change ="setValue" | ||||
|       /> --> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| // import vSelect from 'vue-select' | ||||
| export default { | ||||
|   // components: {vSelect}, | ||||
|   props: { | ||||
|     type: { | ||||
|       type: String, | ||||
|       default: null | ||||
|     }, | ||||
|     Options: { | ||||
|       type: [Array, Object], | ||||
|       required: false, | ||||
|       default () { | ||||
|         return [] | ||||
|       } | ||||
|     }, | ||||
|     getData: { | ||||
|       type: Object, | ||||
|       default () { | ||||
|         return {} | ||||
|       } | ||||
|     }, | ||||
|     currentData: { | ||||
|       type: [String, Number], | ||||
|       default: null | ||||
|     } | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       selected: null, | ||||
|       list: [] | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     window.setTimeout(() => { | ||||
|       this.setList() | ||||
|       if (this.currentData !== null || this.currentData !== '') { | ||||
|         this.defaultValue(this.currentData) | ||||
|       } | ||||
|     }, 1000) | ||||
|   }, | ||||
|   methods: { | ||||
|     setList () { | ||||
|       if (this.type === 'currencies') { | ||||
|         for (let i = 0; i < this.Options.length; i++) { | ||||
|           this.list.push(this.Options[i].name + ' - ' + this.Options[i].code) | ||||
|         } | ||||
|       } else if (this.type === 'time_zones' || this.type === 'languages' || this.type === 'date_formats') { | ||||
|         for (let key in this.Options) { | ||||
|           this.list.push(this.Options[key]) | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     setValue (val) { | ||||
|       if (this.type === 'currencies') { | ||||
|         for (let i = 0; i < this.Options.length; i++) { | ||||
|           if (val === this.Options[i].name + ' - ' + this.Options[i].code) { | ||||
|             this.getData.currency = this.Options[i].id | ||||
|             break | ||||
|           } | ||||
|         } | ||||
|       } else if (this.type === 'time_zones') { | ||||
|         for (let key in this.Options) { | ||||
|           if (val === this.Options[key]) { | ||||
|             this.getData.time_zone = key | ||||
|             break | ||||
|           } | ||||
|         } | ||||
|       } else if (this.type === 'languages') { | ||||
|         for (let key in this.Options) { | ||||
|           if (val === this.Options[key]) { | ||||
|             this.getData.language = key | ||||
|             break | ||||
|           } | ||||
|         } | ||||
|       } else if (this.type === 'date_formats') { | ||||
|         for (let key in this.Options) { | ||||
|           if (val === this.Options[key]) { | ||||
|             this.getData.date_format = key | ||||
|             break | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     defaultValue (val) { | ||||
|       if (this.type === 'currencies') { | ||||
|         for (let i = 0; i < this.Options.length; i++) { | ||||
|           if (Number(val) === this.Options[i].id) { | ||||
|             this.selected = this.Options[i].name + ' - ' + this.Options[i].code | ||||
|             break | ||||
|           } | ||||
|         } | ||||
|       } else if (this.type === 'time_zones') { | ||||
|         for (let key in this.Options) { | ||||
|           if (val === key) { | ||||
|             this.selected = this.Options[key] | ||||
|             break | ||||
|           } | ||||
|         } | ||||
|       } else if (this.type === 'languages') { | ||||
|         for (let key in this.Options) { | ||||
|           if (val === key) { | ||||
|             this.selected = this.Options[key] | ||||
|             break | ||||
|           } | ||||
|         } | ||||
|       } else if (this.type === 'date_formats') { | ||||
|         for (let key in this.Options) { | ||||
|           if (val === key) { | ||||
|             this.selected = this.Options[key] | ||||
|             break | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										678
									
								
								resources/assets/js/views/customers/Create.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										678
									
								
								resources/assets/js/views/customers/Create.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,678 @@ | ||||
| <template> | ||||
|   <div class="customer-create main-content"> | ||||
|     <form action="" @submit.prevent="submitCustomerData"> | ||||
|       <div class="page-header"> | ||||
|         <h3 class="page-title">{{ isEdit ? $t('customers.edit_customer') : $t('customers.new_customer') }}</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/customers">{{ $tc('customers.customer', 2) }}</router-link></li> | ||||
|           <li class="breadcrumb-item">{{ isEdit ? $t('customers.edit_customer') : $t('customers.new_customer') }}</li> | ||||
|         </ol> | ||||
|         <div class="page-actions header-button-container"> | ||||
|           <base-button | ||||
|             :loading="isLoading" | ||||
|             :disabled="isLoading" | ||||
|             :tabindex="23" | ||||
|             icon="save" | ||||
|             color="theme" | ||||
|             type="submit" | ||||
|           > | ||||
|             {{ isEdit ? $t('customers.update_customer') : $t('customers.save_customer') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="customer-card card"> | ||||
|         <div class="card-body"> | ||||
|           <div class="row"> | ||||
|             <div class="section-title col-sm-2">{{ $t('customers.basic_info') }}</div> | ||||
|             <div class="col-sm-5"> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.display_name') }}</label><span class="text-danger"> *</span> | ||||
|                 <base-input | ||||
|                   :invalid="$v.formData.name.$error" | ||||
|                   v-model="formData.name" | ||||
|                   focus | ||||
|                   type="text" | ||||
|                   name="name" | ||||
|                   tab-index="1" | ||||
|                   @input="$v.formData.name.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.formData.name.$error"> | ||||
|                   <span v-if="!$v.formData.name.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|                   <span v-if="!$v.formData.name.minLength" class="text-danger"> {{ $tc('validation.name_min_length', $v.formData.name.$params.minLength.min, { count: $v.formData.name.$params.minLength.min }) }} </span> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.email') }}</label> | ||||
|                 <base-input | ||||
|                   :invalid="$v.formData.email.$error" | ||||
|                   v-model.trim="formData.email" | ||||
|                   type="text" | ||||
|                   name="email" | ||||
|                   tab-index="3" | ||||
|                   @input="$v.formData.email.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.formData.email.$error"> | ||||
|                   <span v-if="!$v.formData.email.email" class="text-danger"> {{ $tc('validation.email_incorrect') }} </span> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.primary_currency') }}</label> | ||||
|                 <base-select | ||||
|                   v-model="currency" | ||||
|                   :options="currencies" | ||||
|                   :allow-empty="false" | ||||
|                   :searchable="true" | ||||
|                   :show-labels="false" | ||||
|                   :tabindex="5" | ||||
|                   :placeholder="$t('customers.select_currency')" | ||||
|                   label="name" | ||||
|                   track-by="id" | ||||
|                 /> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="col-sm-5"> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.primary_contact_name') }}</label> | ||||
|                 <base-input | ||||
|                   v-model.trim="formData.contact_name" | ||||
|                   :label="$t('customers.contact_name')" | ||||
|                   type="text" | ||||
|                   tab-index="2" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.phone') }}</label> | ||||
|                 <base-input | ||||
|                   :invalid="$v.formData.phone.$error" | ||||
|                   v-model.trim="formData.phone" | ||||
|                   type="text" | ||||
|                   name="phone" | ||||
|                   tab-index="4" | ||||
|                   @input="$v.formData.phone.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.formData.phone.$error"> | ||||
|                   <span v-if="!$v.formData.phone.numeric" class="text-danger">{{ $tc('validation.numbers_only') }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.website') }}</label> | ||||
|                 <base-input | ||||
|                   v-model="formData.website" | ||||
|                   :invalid="$v.formData.website.$error" | ||||
|                   type="url" | ||||
|                   @input="$v.formData.website.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.formData.website.$error"> | ||||
|                   <span v-if="!$v.formData.website.url" class="text-danger">{{ $tc('validation.invalid_url') }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|           <hr> <!-- first row complete  --> | ||||
|           <div class="row"> | ||||
|             <div class="section-title col-sm-2">{{ $t('customers.billing_address') }}</div> | ||||
|             <div class="col-sm-5"> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.name') }}</label> | ||||
|                 <base-input | ||||
|                   v-model.trim="billing.name" | ||||
|                   type="text" | ||||
|                   name="address_name" | ||||
|                   tab-index="7" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.state') }}</label> | ||||
|                 <base-select | ||||
|                   v-model="billing_state" | ||||
|                   :options="billingStates" | ||||
|                   :searchable="true" | ||||
|                   :show-labels="false" | ||||
|                   :tabindex="9" | ||||
|                   :disabled="isDisabledBillingState" | ||||
|                   :placeholder="$t('general.select_state')" | ||||
|                   label="name" | ||||
|                   track-by="id" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.address') }}</label> | ||||
|                 <base-text-area | ||||
|                   v-model.trim="billing.address_street_1" | ||||
|                   :tabindex="11" | ||||
|                   :placeholder="$t('general.street_1')" | ||||
|                   type="text" | ||||
|                   name="billing_street1" | ||||
|                   rows="2" | ||||
|                   @input="$v.billing.address_street_1.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.billing.address_street_1.$error"> | ||||
|                   <span v-if="!$v.billing.address_street_1.maxLength" class="text-danger">{{ $t('validation.address_maxlength') }}</span> | ||||
|                 </div> | ||||
|                 <base-text-area | ||||
|                   :tabindex="12" | ||||
|                   v-model.trim="billing.address_street_2" | ||||
|                   :placeholder="$t('general.street_2')" | ||||
|                   type="text" | ||||
|                   name="billing_street2" | ||||
|                   rows="2" | ||||
|                   @input="$v.billing.address_street_2.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.billing.address_street_2.$error"> | ||||
|                   <span v-if="!$v.billing.address_street_2.maxLength" class="text-danger">{{ $t('validation.address_maxlength') }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="col-sm-5"> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.country') }}</label> | ||||
|                 <base-select | ||||
|                   v-model="billing_country" | ||||
|                   :options="billingCountries" | ||||
|                   :searchable="true" | ||||
|                   :show-labels="false" | ||||
|                   :allow-empty="false" | ||||
|                   :tabindex="8" | ||||
|                   :placeholder="$t('general.select_country')" | ||||
|                   label="name" | ||||
|                   track-by="id" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.city') }}</label> | ||||
|                 <base-select | ||||
|                   v-model="billing_city" | ||||
|                   :options="billingCities" | ||||
|                   :searchable="true" | ||||
|                   :show-labels="false" | ||||
|                   :disabled="isDisabledBillingCity" | ||||
|                   :tabindex="10" | ||||
|                   :placeholder="$t('general.select_city')" | ||||
|                   label="name" | ||||
|                   track-by="id" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.phone') }}</label> | ||||
|                 <base-input | ||||
|                   :invalid="$v.billing.phone.$error" | ||||
|                   v-model.trim="billing.phone" | ||||
|                   type="text" | ||||
|                   name="phone" | ||||
|                   tab-index="13" | ||||
|                   @input="$v.billing.phone.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.billing.phone.$error"> | ||||
|                   <span v-if="!$v.billing.phone.numberic" class="text-danger">{{ $tc('validation.numbers_only') }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.zip_code') }}</label> | ||||
|                 <base-input | ||||
|                   v-model.trim="billing.zip" | ||||
|                   type="text" | ||||
|                   name="zip" | ||||
|                   tab-index="14" | ||||
|                 /> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|           <hr> <!-- second row complete  --> | ||||
|           <div class="row same-address-checkbox-container"> | ||||
|             <div class="p-1"> | ||||
|               <base-button ref="sameAddress" icon="copy" color="theme" class="btn-sm" @click="copyAddress(true)"> | ||||
|                 {{ $t('customers.copy_billing_address') }} | ||||
|               </base-button> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
|           <div class="row"> | ||||
|             <div class="section-title col-sm-2"> | ||||
|               {{ $t('customers.shipping_address') }} | ||||
|             </div> | ||||
|             <div class="col-sm-5"> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.name') }}</label> | ||||
|                 <base-input | ||||
|                   v-model.trim="shipping.name" | ||||
|                   type="text" | ||||
|                   name="address_name" | ||||
|                   tab-index="15" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.state') }}</label> | ||||
|                 <base-select | ||||
|                   v-model="shipping_state" | ||||
|                   :options="shippingStates" | ||||
|                   :searchable="true" | ||||
|                   :show-labels="false" | ||||
|                   :tabindex="17" | ||||
|                   :disabled="isDisabledShippingState" | ||||
|                   :placeholder="$t('general.select_state')" | ||||
|                   label="name" | ||||
|                   track-by="id" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.address') }}</label> | ||||
|                 <base-text-area | ||||
|                   v-model.trim="shipping.address_street_1" | ||||
|                   :tabindex="19" | ||||
|                   :placeholder="$t('general.street_1')" | ||||
|                   type="text" | ||||
|                   name="street_1" | ||||
|                   rows="2" | ||||
|                   @input="$v.shipping.address_street_1.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.shipping.address_street_1.$error"> | ||||
|                   <span v-if="!$v.shipping.address_street_1.maxLength" class="text-danger">{{ $t('validation.address_maxlength') }}</span> | ||||
|                 </div> | ||||
|                 <base-text-area | ||||
|                   v-model.trim="shipping.address_street_2" | ||||
|                   :tabindex="20" | ||||
|                   :placeholder="$t('general.street_2')" | ||||
|                   type="text" | ||||
|                   name="street_2" | ||||
|                   rows="2" | ||||
|                   @input="$v.shipping.address_street_2.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.shipping.address_street_2.$error"> | ||||
|                   <span v-if="!$v.shipping.address_street_2.maxLength" class="text-danger">{{ $t('validation.address_maxlength') }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="col-sm-5"> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.country') }}</label> | ||||
|                 <base-select | ||||
|                   v-model="shipping_country" | ||||
|                   :options="shippingCountries" | ||||
|                   :searchable="true" | ||||
|                   :show-labels="false" | ||||
|                   :tabindex="16" | ||||
|                   :allow-empty="false" | ||||
|                   :placeholder="$t('general.select_country')" | ||||
|                   label="name" | ||||
|                   track-by="id" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.city') }}</label> | ||||
|                 <base-select | ||||
|                   v-model="shipping_city" | ||||
|                   :options="shippingCities" | ||||
|                   :searchable="true" | ||||
|                   :show-labels="false" | ||||
|                   :tabindex="18" | ||||
|                   :disabled="isDisabledShippingCity" | ||||
|                   :placeholder="$t('general.select_city')" | ||||
|                   label="name" | ||||
|                   track-by="id" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.phone') }}</label> | ||||
|                 <base-input | ||||
|                   :invalid="$v.shipping.phone.$error" | ||||
|                   v-model.trim="shipping.phone" | ||||
|                   type="text" | ||||
|                   name="phone" | ||||
|                   tab-index="21" | ||||
|                   @input="$v.shipping.phone.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.shipping.phone.$error"> | ||||
|                   <span v-if="!$v.shipping.phone.numberic" class="text-danger">{{ $tc('validation.numbers_only') }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('customers.zip_code') }}</label> | ||||
|                 <base-input | ||||
|                   v-model.trim="shipping.zip" | ||||
|                   type="text" | ||||
|                   name="zip" | ||||
|                   tab-index="22" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="form-group collapse-button-container"> | ||||
|                 <base-button | ||||
|                   :tabindex="23" | ||||
|                   icon="save" | ||||
|                   color="theme" | ||||
|                   class="collapse-button" | ||||
|                   type="submit" | ||||
|                 > | ||||
|                   {{ $t('customers.save_customer') }} | ||||
|                 </base-button> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </form> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
| import MultiSelect from 'vue-multiselect' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import AddressStub from '../../stub/address' | ||||
| const { required, minLength, email, numeric, url, maxLength } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   components: { MultiSelect }, | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       isCopyFromBilling: false, | ||||
|       isLoading: false, | ||||
|       formData: { | ||||
|         name: null, | ||||
|         contact_name: null, | ||||
|         email: null, | ||||
|         phone: null, | ||||
|         currency_id: null, | ||||
|         website: null, | ||||
|         addresses: [] | ||||
|       }, | ||||
|       currency: null, | ||||
|       billing: { | ||||
|         name: null, | ||||
|         country_id: null, | ||||
|         state_id: null, | ||||
|         city_id: null, | ||||
|         phone: null, | ||||
|         zip: null, | ||||
|         address_street_1: null, | ||||
|         address_street_2: null, | ||||
|         type: 'billing' | ||||
|       }, | ||||
|       shipping: { | ||||
|         name: null, | ||||
|         country_id: null, | ||||
|         state_id: null, | ||||
|         city_id: null, | ||||
|         phone: null, | ||||
|         zip: null, | ||||
|         address_street_1: null, | ||||
|         address_street_2: null, | ||||
|         type: 'shipping' | ||||
|       }, | ||||
|       currencyList: [], | ||||
|       isDisabledBillingState: true, | ||||
|       isDisabledBillingCity: true, | ||||
|       isDisabledShippingState: true, | ||||
|       isDisabledShippingCity: true, | ||||
|  | ||||
|       billing_country: null, | ||||
|       billing_city: null, | ||||
|       billing_state: null, | ||||
|  | ||||
|       shipping_country: null, | ||||
|       shipping_city: null, | ||||
|       shipping_state: null, | ||||
|  | ||||
|       billingCountries: [], | ||||
|       billingStates: [], | ||||
|       billingCities: [], | ||||
|  | ||||
|       shippingCountries: [], | ||||
|       shippingStates: [], | ||||
|       shippingCities: [] | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     formData: { | ||||
|       name: { | ||||
|         required, | ||||
|         minLength: minLength(3) | ||||
|       }, | ||||
|       email: { | ||||
|         email | ||||
|       }, | ||||
|       phone: { | ||||
|         numeric | ||||
|       }, | ||||
|       website: { | ||||
|         url | ||||
|       } | ||||
|     }, | ||||
|     billing: { | ||||
|       phone: { | ||||
|         numeric | ||||
|       }, | ||||
|       address_street_1: { | ||||
|         maxLength: maxLength(255) | ||||
|       }, | ||||
|       address_street_2: { | ||||
|         maxLength: maxLength(255) | ||||
|       } | ||||
|     }, | ||||
|     shipping: { | ||||
|       phone: { | ||||
|         numeric | ||||
|       }, | ||||
|       address_street_1: { | ||||
|         maxLength: maxLength(255) | ||||
|       }, | ||||
|       address_street_2: { | ||||
|         maxLength: maxLength(255) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('currency', [ | ||||
|       'defaultCurrency', | ||||
|       'currencies' | ||||
|     ]), | ||||
|     isEdit () { | ||||
|       if (this.$route.name === 'customers.edit') { | ||||
|         return true | ||||
|       } | ||||
|       return false | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     billing_country (newCountry) { | ||||
|       if (newCountry) { | ||||
|         this.billing.country_id = newCountry.id | ||||
|         this.isDisabledBillingState = false | ||||
|         this.billing_state = null | ||||
|         this.billing_city = null | ||||
|         this.fetchBillingState() | ||||
|       } | ||||
|     }, | ||||
|     billing_state (newState) { | ||||
|       if (newState) { | ||||
|         this.billing.state_id = newState.id | ||||
|         this.isDisabledBillingCity = false | ||||
|         this.billing_city = null | ||||
|         this.fetchBillingCities() | ||||
|         return true | ||||
|       } | ||||
|       this.billing_city = null | ||||
|       this.isDisabledBillingCity = true | ||||
|       return true | ||||
|     }, | ||||
|     billing_city (newCity) { | ||||
|       if (newCity) { | ||||
|         this.billing.city_id = newCity.id | ||||
|       } | ||||
|     }, | ||||
|     shipping_country (newCountry) { | ||||
|       if (newCountry) { | ||||
|         this.shipping.country_id = newCountry.id | ||||
|         this.isDisabledShippingState = false | ||||
|         this.fetchShippingState() | ||||
|         if (this.isCopyFromBilling) { | ||||
|           return true | ||||
|         } | ||||
|         this.shipping_state = null | ||||
|         this.shipping_city = null | ||||
|         return true | ||||
|       } | ||||
|     }, | ||||
|     shipping_state (newState) { | ||||
|       if (newState) { | ||||
|         this.shipping.state_id = newState.id | ||||
|         this.isDisabledShippingCity = false | ||||
|         this.fetchShippingCities() | ||||
|         if (this.isCopyFromBilling) { | ||||
|           this.isCopyFromBilling = false | ||||
|           return true | ||||
|         } | ||||
|         this.shipping_city = null | ||||
|         return true | ||||
|       } | ||||
|       this.shipping_city = null | ||||
|       this.isDisabledShippingCity = true | ||||
|       return true | ||||
|     }, | ||||
|     shipping_city (newCity) { | ||||
|       if (newCity) { | ||||
|         this.shipping.city_id = newCity.id | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     this.fetchCountry() | ||||
|     if (this.isEdit) { | ||||
|       this.loadCustomer() | ||||
|     } else { | ||||
|       this.currency = this.defaultCurrency | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('customer', [ | ||||
|       'addCustomer', | ||||
|       'fetchCustomer', | ||||
|       'updateCustomer' | ||||
|     ]), | ||||
|     async loadCustomer () { | ||||
|       let { data: { customer, currencies, currency } } = await this.fetchCustomer(this.$route.params.id) | ||||
|       this.formData = customer | ||||
|  | ||||
|       if (customer.billing_address) { | ||||
|         this.billing = customer.billing_address | ||||
|  | ||||
|         if (customer.billing_address.country_id) { | ||||
|           this.billing_country = this.billingCountries.find((c) => c.id === customer.billing_address.country_id) | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (customer.shipping_address) { | ||||
|         this.shipping = customer.shipping_address | ||||
|  | ||||
|         if (customer.shipping_address.country_id) { | ||||
|           this.shipping_country = this.shippingCountries.find((c) => c.id === customer.shipping_address.country_id) | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       this.currencyList = currencies | ||||
|       this.formData.currency_id = customer.currency_id | ||||
|       this.currency = currency | ||||
|     }, | ||||
|     async fetchCountry () { | ||||
|       let res = await window.axios.get('/api/countries') | ||||
|       if (res) { | ||||
|         this.billingCountries = res.data.countries | ||||
|         this.shippingCountries = res.data.countries | ||||
|       } | ||||
|     }, | ||||
|     copyAddress (val) { | ||||
|       if (val === true) { | ||||
|         this.isCopyFromBilling = true | ||||
|         this.shipping = {...this.billing, type: 'shipping'} | ||||
|         this.shipping_country = this.billing_country | ||||
|         this.shipping_state = this.billing_state | ||||
|         this.shipping_city = this.billing_city | ||||
|       } else { | ||||
|         this.shipping = {...AddressStub, type: 'shipping'} | ||||
|         this.shipping_country = null | ||||
|         this.shipping_state = null | ||||
|         this.shipping_city = null | ||||
|       } | ||||
|     }, | ||||
|     async submitCustomerData () { | ||||
|       this.$v.formData.$touch() | ||||
|  | ||||
|       if (this.$v.$invalid) { | ||||
|         return true | ||||
|       } | ||||
|       this.formData.addresses = [{...this.billing}, {...this.shipping}] | ||||
|  | ||||
|       if (this.isEdit) { | ||||
|         if (this.currency) { | ||||
|           this.formData.currency_id = this.currency.id | ||||
|         } | ||||
|         this.isLoading = true | ||||
|         let response = await this.updateCustomer(this.formData) | ||||
|  | ||||
|         if (response.data) { | ||||
|           window.toastr['success'](this.$t('customers.updated_message')) | ||||
|           this.$router.push('/admin/customers') | ||||
|           this.isLoading = false | ||||
|           return true | ||||
|         } | ||||
|  | ||||
|         window.toastr['error'](response.data.error) | ||||
|       } else { | ||||
|         this.isLoading = true | ||||
|         if (this.currency) { | ||||
|           this.isLoading = true | ||||
|           this.formData.currency_id = this.currency.id | ||||
|         } | ||||
|  | ||||
|         let response = await this.addCustomer(this.formData) | ||||
|  | ||||
|         if (response.data.success) { | ||||
|           window.toastr['success'](this.$t('customers.created_message')) | ||||
|           this.$router.push('/admin/customers') | ||||
|           this.isLoading = false | ||||
|           return true | ||||
|         } | ||||
|  | ||||
|         window.toastr['error'](response.data.error) | ||||
|       } | ||||
|     }, | ||||
|     async fetchBillingState () { | ||||
|       let res = await window.axios.get(`/api/states/${this.billing_country.id}`) | ||||
|       if (res) { | ||||
|         this.billingStates = res.data.states | ||||
|       } | ||||
|       if (this.isEdit) { | ||||
|         this.billing_state = this.billingStates.find((state) => state.id === this.billing.state_id) | ||||
|       } | ||||
|     }, | ||||
|     async fetchBillingCities () { | ||||
|       let res = await window.axios.get(`/api/cities/${this.billing_state.id}`) | ||||
|       if (res) { | ||||
|         this.billingCities = res.data.cities | ||||
|       } | ||||
|       if (this.isEdit) { | ||||
|         this.billing_city = this.billingCities.find((city) => city.id === this.billing.city_id) | ||||
|       } | ||||
|     }, | ||||
|     async fetchShippingState () { | ||||
|       let res = await window.axios.get(`/api/states/${this.shipping_country.id}`) | ||||
|       if (res) { | ||||
|         this.shippingStates = res.data.states | ||||
|       } | ||||
|       if (this.isEdit) { | ||||
|         this.shipping_state = this.shippingStates.find((s) => s.id === this.shipping.state_id) | ||||
|       } | ||||
|     }, | ||||
|     async fetchShippingCities () { | ||||
|       let res = await window.axios.get(`/api/cities/${this.shipping_state.id}`) | ||||
|       if (res) { | ||||
|         this.shippingCities = res.data.cities | ||||
|       } | ||||
|       if (this.isEdit) { | ||||
|         this.shipping_city = this.shippingCities.find((c) => c.id === this.shipping.city_id) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										383
									
								
								resources/assets/js/views/customers/Index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										383
									
								
								resources/assets/js/views/customers/Index.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,383 @@ | ||||
| <template> | ||||
|   <div class="customer-create main-content"> | ||||
|     <div class="page-header"> | ||||
|       <h3 class="page-title">{{ $t('customers.title') }}</h3> | ||||
|       <ol class="breadcrumb"> | ||||
|         <li class="breadcrumb-item"> | ||||
|           <router-link | ||||
|             slot="item-title" | ||||
|             to="dashboard"> | ||||
|             {{ $t('general.home') }} | ||||
|           </router-link> | ||||
|         </li> | ||||
|         <li class="breadcrumb-item"> | ||||
|           <router-link | ||||
|             slot="item-title" | ||||
|             to="#"> | ||||
|             {{ $tc('customers.customer',2) }} | ||||
|           </router-link> | ||||
|         </li> | ||||
|       </ol> | ||||
|       <div class="page-actions row"> | ||||
|         <div class="col-xs-2 mr-4"> | ||||
|           <base-button | ||||
|             v-show="totalCustomers || filtersApplied" | ||||
|             :outline="true" | ||||
|             :icon="filterIcon" | ||||
|             size="large" | ||||
|             color="theme" | ||||
|             right-icon | ||||
|             @click="toggleFilter" | ||||
|           > | ||||
|             {{ $t('general.filter') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|         <router-link slot="item-title" class="col-xs-2" to="customers/create"> | ||||
|           <base-button | ||||
|             size="large" | ||||
|             icon="plus" | ||||
|             color="theme"> | ||||
|             {{ $t('customers.new_customer') }} | ||||
|           </base-button> | ||||
|         </router-link> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <transition name="fade"> | ||||
|       <div v-show="showFilters" class="filter-section"> | ||||
|         <div class="row"> | ||||
|           <div class="col-sm-4"> | ||||
|             <label class="form-label">{{ $t('customers.display_name') }}</label> | ||||
|             <base-input | ||||
|               v-model="filters.display_name" | ||||
|               type="text" | ||||
|               name="name" | ||||
|               autocomplete="off" | ||||
|             /> | ||||
|           </div> | ||||
|           <div class="col-sm-4"> | ||||
|             <label class="form-label">{{ $t('customers.contact_name') }}</label> | ||||
|             <base-input | ||||
|               v-model="filters.contact_name" | ||||
|               type="text" | ||||
|               name="address_name" | ||||
|               autocomplete="off" | ||||
|             /> | ||||
|           </div> | ||||
|           <div class="col-sm-4"> | ||||
|             <label class="form-label">{{ $t('customers.phone') }}</label> | ||||
|             <base-input | ||||
|               v-model="filters.phone" | ||||
|               type="text" | ||||
|               name="phone" | ||||
|               autocomplete="off" | ||||
|             /> | ||||
|           </div> | ||||
|           <label class="clear-filter" @click="clearFilter">{{ $t('general.clear_all') }}</label> | ||||
|         </div> | ||||
|       </div> | ||||
|     </transition> | ||||
|  | ||||
|     <div v-cloak v-show="showEmptyScreen" class="col-xs-1 no-data-info" align="center"> | ||||
|       <astronaut-icon class="mt-5 mb-4"/> | ||||
|       <div class="row" align="center"> | ||||
|         <label class="col title">{{ $t('customers.no_customers') }}</label> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <label class="description col mt-1" align="center">{{ $t('customers.list_of_customers') }}</label> | ||||
|       </div> | ||||
|       <div class="btn-container"> | ||||
|         <base-button | ||||
|           :outline="true" | ||||
|           color="theme" | ||||
|           class="mt-3" | ||||
|           size="large" | ||||
|           @click="$router.push('customers/create')" | ||||
|         > | ||||
|           {{ $t('customers.add_new_customer') }} | ||||
|         </base-button> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <div v-show="!showEmptyScreen" class="table-container"> | ||||
|       <div class="table-actions mt-5"> | ||||
|         <p class="table-stats">{{ $t('general.showing') }}: <b>{{ customers.length }}</b> {{ $t('general.of') }} <b>{{ totalCustomers }}</b></p> | ||||
|  | ||||
|         <transition name="fade"> | ||||
|           <v-dropdown v-if="selectedCustomers.length" :show-arrow="false"> | ||||
|             <span slot="activator" href="#" class="table-actions-button dropdown-toggle"> | ||||
|               {{ $t('general.actions') }} | ||||
|             </span> | ||||
|             <v-dropdown-item> | ||||
|               <div class="dropdown-item" @click="removeMultipleCustomers"> | ||||
|                 <font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" /> | ||||
|                 {{ $t('general.delete') }} | ||||
|               </div> | ||||
|             </v-dropdown-item> | ||||
|           </v-dropdown> | ||||
|         </transition> | ||||
|       </div> | ||||
|  | ||||
|       <div class="custom-control custom-checkbox"> | ||||
|         <input | ||||
|           id="select-all" | ||||
|           v-model="selectAllFieldStatus" | ||||
|           type="checkbox" | ||||
|           class="custom-control-input" | ||||
|           @change="selectAllCustomers" | ||||
|         > | ||||
|         <label for="select-all" class="custom-control-label selectall"> | ||||
|           <span class="select-all-label">{{ $t('general.select_all') }} </span> | ||||
|         </label> | ||||
|       </div> | ||||
|  | ||||
|       <table-component | ||||
|         ref="table" | ||||
|         :show-filter="false" | ||||
|         :data="fetchData" | ||||
|         table-class="table" | ||||
|       > | ||||
|         <table-column | ||||
|           :sortable="false" | ||||
|           :filterable="false" | ||||
|           cell-class="no-click" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <div class="custom-control custom-checkbox"> | ||||
|               <input | ||||
|                 :id="row.id" | ||||
|                 v-model="selectField" | ||||
|                 :value="row.id" | ||||
|                 type="checkbox" | ||||
|                 class="custom-control-input" | ||||
|               > | ||||
|               <label :for="row.id" class="custom-control-label" /> | ||||
|             </div> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <table-column | ||||
|           :label="$t('customers.display_name')" | ||||
|           show="name" | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('customers.contact_name')" | ||||
|           show="contact_name" | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('customers.phone')" | ||||
|           show="phone" | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('customers.amount_due')" | ||||
|           show="due_amount" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <span> {{ $t('customers.amount_due') }} </span> | ||||
|             <div v-html="$utils.formatMoney(row.due_amount, row.currency)"/> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <table-column | ||||
|           :label="$t('customers.added_on')" | ||||
|           sort-as="created_at" | ||||
|           show="formattedCreatedAt" | ||||
|         /> | ||||
|         <table-column | ||||
|           :sortable="false" | ||||
|           :filterable="false" | ||||
|           cell-class="action-dropdown" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <span> {{ $t('customers.action') }} </span> | ||||
|             <v-dropdown> | ||||
|               <a slot="activator" href="#"> | ||||
|                 <dot-icon /> | ||||
|               </a> | ||||
|               <v-dropdown-item> | ||||
|  | ||||
|                 <router-link :to="{path: `customers/${row.id}/edit`}" class="dropdown-item"> | ||||
|                   <font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon"/> | ||||
|                   {{ $t('general.edit') }} | ||||
|                 </router-link> | ||||
|  | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item> | ||||
|                 <div class="dropdown-item" @click="removeCustomer(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' | ||||
| import { SweetModal, SweetModalTab } from 'sweet-modal-vue' | ||||
| import DotIcon from '../../components/icon/DotIcon' | ||||
| import AstronautIcon from '../../components/icon/AstronautIcon' | ||||
| import BaseButton from '../../../js/components/base/BaseButton' | ||||
| import { request } from 'http' | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     DotIcon, | ||||
|     AstronautIcon, | ||||
|     SweetModal, | ||||
|     SweetModalTab, | ||||
|     BaseButton | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       showFilters: false, | ||||
|       filtersApplied: false, | ||||
|       isRequestOngoing: true, | ||||
|       filters: { | ||||
|         display_name: '', | ||||
|         contact_name: '', | ||||
|         phone: '' | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     showEmptyScreen () { | ||||
|       return !this.totalCustomers && !this.isRequestOngoing && !this.filtersApplied | ||||
|     }, | ||||
|     filterIcon () { | ||||
|       return (this.showFilters) ? 'times' : 'filter' | ||||
|     }, | ||||
|     ...mapGetters('customer', [ | ||||
|       'customers', | ||||
|       'selectedCustomers', | ||||
|       'totalCustomers', | ||||
|       'selectAllField' | ||||
|     ]), | ||||
|     selectField: { | ||||
|       get: function () { | ||||
|         return this.selectedCustomers | ||||
|       }, | ||||
|       set: function (val) { | ||||
|         this.selectCustomer(val) | ||||
|       } | ||||
|     }, | ||||
|     selectAllFieldStatus: { | ||||
|       get: function () { | ||||
|         return this.selectAllField | ||||
|       }, | ||||
|       set: function (val) { | ||||
|         this.setSelectAllState(val) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     filters: { | ||||
|       handler: 'setFilters', | ||||
|       deep: true | ||||
|     } | ||||
|   }, | ||||
|   destroyed () { | ||||
|     if (this.selectAllField) { | ||||
|       this.selectAllCustomers() | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('customer', [ | ||||
|       'fetchCustomers', | ||||
|       'selectAllCustomers', | ||||
|       'selectCustomer', | ||||
|       'deleteCustomer', | ||||
|       'deleteMultipleCustomers', | ||||
|       'setSelectAllState' | ||||
|     ]), | ||||
|     refreshTable () { | ||||
|       this.$refs.table.refresh() | ||||
|     }, | ||||
|     async fetchData ({ page, filter, sort }) { | ||||
|       let data = { | ||||
|         display_name: this.filters.display_name, | ||||
|         contact_name: this.filters.contact_name, | ||||
|         phone: this.filters.phone, | ||||
|         orderByField: sort.fieldName || 'created_at', | ||||
|         orderBy: sort.order || 'desc', | ||||
|         page | ||||
|       } | ||||
|  | ||||
|       this.isRequestOngoing = true | ||||
|       let response = await this.fetchCustomers(data) | ||||
|       this.isRequestOngoing = false | ||||
|  | ||||
|       return { | ||||
|         data: response.data.customers.data, | ||||
|         pagination: { | ||||
|           totalPages: response.data.customers.last_page, | ||||
|           currentPage: page | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     setFilters () { | ||||
|       this.filtersApplied = true | ||||
|       this.refreshTable() | ||||
|     }, | ||||
|     clearFilter () { | ||||
|       this.filters = { | ||||
|         display_name: '', | ||||
|         contact_name: '', | ||||
|         phone: '' | ||||
|       } | ||||
|  | ||||
|       this.$nextTick(() => { | ||||
|         this.filtersApplied = false | ||||
|       }) | ||||
|     }, | ||||
|     toggleFilter () { | ||||
|       if (this.showFilters && this.filtersApplied) { | ||||
|         this.clearFilter() | ||||
|         this.refreshTable() | ||||
|       } | ||||
|  | ||||
|       this.showFilters = !this.showFilters | ||||
|     }, | ||||
|     async removeCustomer (id) { | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$tc('customers.confirm_delete'), | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let res = await this.deleteCustomer(id) | ||||
|           if (res.data.success) { | ||||
|             window.toastr['success'](this.$tc('customers.deleted_message')) | ||||
|             this.refreshTable() | ||||
|             return true | ||||
|           } else if (request.data.error) { | ||||
|             window.toastr['error'](res.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     async removeMultipleCustomers () { | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$tc('customers.confirm_delete', 2), | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let request = await this.deleteMultipleCustomers() | ||||
|           if (request.data.success) { | ||||
|             window.toastr['success'](this.$tc('customers.deleted_message', 2)) | ||||
|             this.refreshTable() | ||||
|           } else if (request.data.error) { | ||||
|             window.toastr['error'](request.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										489
									
								
								resources/assets/js/views/dashboard/Dashboard.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										489
									
								
								resources/assets/js/views/dashboard/Dashboard.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,489 @@ | ||||
| <template> | ||||
|   <div id="app" class="main-content"> | ||||
|     <div class="row"> | ||||
|       <div class="dash-item col-sm-6"> | ||||
|         <router-link slot="item-title" to="/admin/invoices"> | ||||
|           <div class="dashbox"> | ||||
|             <div class="desc"> | ||||
|               <span | ||||
|                 v-if="isLoaded" | ||||
|                 class="amount" | ||||
|               > | ||||
|                 <div v-html="$utils.formatMoney(getTotalDueAmount, defaultCurrency)"/> | ||||
|               </span> | ||||
|               <span class="title"> | ||||
|                 {{ $t('dashboard.cards.due_amount') }} | ||||
|               </span> | ||||
|             </div> | ||||
|             <div class="icon"> | ||||
|               <dollar-icon class="card-icon" /> | ||||
|             </div> | ||||
|           </div> | ||||
|         </router-link> | ||||
|       </div> | ||||
|       <div class="dash-item col-sm-6"> | ||||
|         <router-link slot="item-title" to="/admin/customers"> | ||||
|           <div class="dashbox"> | ||||
|             <div class="desc"> | ||||
|               <span v-if="isLoaded" | ||||
|                     class="amount" > | ||||
|                 {{ getContacts }} | ||||
|               </span> | ||||
|               <span class="title"> | ||||
|                 {{ $t('dashboard.cards.customers') }} | ||||
|               </span> | ||||
|             </div> | ||||
|             <div class="icon"> | ||||
|               <contact-icon class="card-icon" /> | ||||
|             </div> | ||||
|           </div> | ||||
|         </router-link> | ||||
|       </div> | ||||
|       <div class="dash-item col-sm-6"> | ||||
|         <router-link slot="item-title" to="/admin/invoices"> | ||||
|           <div class="dashbox"> | ||||
|             <div class="desc"> | ||||
|               <span v-if="isLoaded" | ||||
|                     class="amount"> | ||||
|                 {{ getInvoices }} | ||||
|               </span> | ||||
|               <span class="title"> | ||||
|                 {{ $t('dashboard.cards.invoices') }} | ||||
|               </span> | ||||
|             </div> | ||||
|             <div class="icon"> | ||||
|               <invoice-icon class="card-icon" /> | ||||
|             </div> | ||||
|           </div> | ||||
|         </router-link> | ||||
|       </div> | ||||
|       <div class="dash-item col-sm-6"> | ||||
|         <router-link slot="item-title" to="/admin/estimates"> | ||||
|           <div class="dashbox"> | ||||
|             <div class="desc"> | ||||
|               <span v-if="isLoaded" | ||||
|                     class="amount"> | ||||
|                 {{ getEstimates }} | ||||
|               </span> | ||||
|               <span class="title"> | ||||
|                 {{ $t('dashboard.cards.estimates') }} | ||||
|               </span> | ||||
|             </div> | ||||
|             <div class="icon"> | ||||
|               <estimate-icon class="card-icon" /> | ||||
|             </div> | ||||
|           </div> | ||||
|         </router-link> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="row"> | ||||
|       <div class="col-lg-12 mt-2"> | ||||
|         <div class="card dashboard-card"> | ||||
|           <div class="graph-body"> | ||||
|             <div class="card-body col-md-12 col-lg-12 col-xl-10"> | ||||
|               <div class="card-header"> | ||||
|                 <h6><i class="fa fa-line-chart text-primary"/>{{ $t('dashboard.monthly_chart.title') }} </h6> | ||||
|                 <div class="year-selector"> | ||||
|                   <base-select | ||||
|                     v-model="selectedYear" | ||||
|                     :options="years" | ||||
|                     :allow-empty="false" | ||||
|                     :show-labels="false" | ||||
|                     :placeholder="$t('dashboard.select_year')" | ||||
|                   /> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <line-chart | ||||
|                 v-if="isLoaded" | ||||
|                 :format-money="$utils.formatMoney" | ||||
|                 :invoices="getChartInvoices" | ||||
|                 :expenses="getChartExpenses" | ||||
|                 :receipts="getReceiptTotals" | ||||
|                 :income="getNetProfits" | ||||
|                 :labels="getChartMonths" | ||||
|                 class="" | ||||
|               /> | ||||
|             </div> | ||||
|             <div class="chart-desc col-md-12 col-lg-12 col-xl-2"> | ||||
|               <div class="stats"> | ||||
|                 <div class="description"> | ||||
|                   <span class="title"> {{ $t('dashboard.chart_info.total_sales') }} </span> | ||||
|                   <br> | ||||
|                   <span v-if="isLoaded" class="total"> | ||||
|                     <div v-html="$utils.formatMoney(getTotalSales, defaultCurrency)"/> | ||||
|                   </span> | ||||
|                 </div> | ||||
|                 <div class="description"> | ||||
|                   <span class="title"> {{ $t('dashboard.chart_info.total_receipts') }} </span> | ||||
|                   <br> | ||||
|                   <span v-if="isLoaded" class="total" style="color:#00C99C;"> | ||||
|                     <div v-html="$utils.formatMoney(getTotalReceipts, defaultCurrency)"/> | ||||
|                   </span> | ||||
|                 </div> | ||||
|                 <div class="description"> | ||||
|                   <span class="title"> {{ $t('dashboard.chart_info.total_expense') }} </span> | ||||
|                   <br> | ||||
|                   <span v-if="isLoaded" class="total" style="color:#FB7178;"> | ||||
|                     <div v-html="$utils.formatMoney(getTotalExpenses, defaultCurrency)"/> | ||||
|                   </span> | ||||
|                 </div> | ||||
|                 <div class="description"> | ||||
|                   <span class="title"> {{ $t('dashboard.chart_info.net_income') }} </span> | ||||
|                   <br> | ||||
|                   <span class="total" style="color:#5851D8;"> | ||||
|                     <div v-html="$utils.formatMoney(getNetProfit, defaultCurrency)"/> | ||||
|                   </span> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <base-loader v-if="!getLoadedData"/> | ||||
|     <div class="row table-row"> | ||||
|       <div class="col-lg-12 col-xl-6 mt-2"> | ||||
|         <div class="table-header"> | ||||
|           <h6 class="table-title"> | ||||
|             {{ $t('dashboard.recent_invoices_card.title') }} | ||||
|           </h6> | ||||
|           <router-link to="/admin/invoices"> | ||||
|             <base-button | ||||
|               :outline="true" | ||||
|               color="theme" | ||||
|               class="btn-sm" | ||||
|             > | ||||
|               {{ $t('dashboard.recent_invoices_card.view_all') }} | ||||
|             </base-button> | ||||
|           </router-link> | ||||
|         </div> | ||||
|         <div class="dashboard-table"> | ||||
|           <table-component | ||||
|             ref="table" | ||||
|             :data="getDueInvoices" | ||||
|             :show-filter="false" | ||||
|             table-class="table" | ||||
|             class="dashboard" | ||||
|           > | ||||
|             <table-column :label="$t('dashboard.recent_invoices_card.due_on')" show="formattedDueDate" /> | ||||
|             <table-column :label="$t('dashboard.recent_invoices_card.customer')" show="user.name" /> | ||||
|             <table-column :label="$t('dashboard.recent_invoices_card.amount_due')" sort-as="due_amount"> | ||||
|               <template slot-scope="row"> | ||||
|                 <span>{{ $t('dashboard.recent_invoices_card.amount_due') }}</span> | ||||
|                 <div v-html="$utils.formatMoney(row.due_amount, row.user.currency)"/> | ||||
|               </template> | ||||
|             </table-column> | ||||
|             <table-column | ||||
|               :sortable="false" | ||||
|               :filterable="false" | ||||
|               cell-class="action-dropdown no-click" | ||||
|             > | ||||
|               <template slot-scope="row"> | ||||
|                 <v-dropdown> | ||||
|                   <a slot="activator" href="#"> | ||||
|                     <dot-icon /> | ||||
|                   </a> | ||||
|                   <v-dropdown-item> | ||||
|                     <router-link :to="{path: `invoices/${row.id}/edit`}" class="dropdown-item"> | ||||
|                       <font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon"/> | ||||
|                       {{ $t('general.edit') }} | ||||
|                     </router-link> | ||||
|                     <router-link :to="{path: `invoices/${row.id}/view`}" class="dropdown-item"> | ||||
|                       <font-awesome-icon icon="eye" class="dropdown-item-icon" /> | ||||
|                       {{ $t('invoices.view') }} | ||||
|                     </router-link> | ||||
|                   </v-dropdown-item> | ||||
|                   <v-dropdown-item> | ||||
|                     <a class="dropdown-item" href="#" @click="sendInvoice(row.id)" > | ||||
|                       <font-awesome-icon icon="envelope" class="dropdown-item-icon" /> | ||||
|                       {{ $t('invoices.send_invoice') }} | ||||
|                     </a> | ||||
|                   </v-dropdown-item> | ||||
|                   <v-dropdown-item v-if="row.status === 'DRAFT'"> | ||||
|                     <a class="dropdown-item" href="#" @click="sentInvoice(row.id)"> | ||||
|                       <font-awesome-icon icon="check-circle" class="dropdown-item-icon" /> | ||||
|                       {{ $t('invoices.mark_as_sent') }} | ||||
|                     </a> | ||||
|                   </v-dropdown-item> | ||||
|                   <v-dropdown-item> | ||||
|                     <div class="dropdown-item" @click="removeInvoice(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> | ||||
|       <div class="col-lg-12 col-xl-6 mt-2 mob-table"> | ||||
|         <div class="table-header"> | ||||
|           <h6 class="table-title"> | ||||
|             {{ $t('dashboard.recent_estimate_card.title') }} | ||||
|           </h6> | ||||
|           <router-link to="/admin/estimates"> | ||||
|             <base-button | ||||
|               :outline="true" | ||||
|               color="theme" | ||||
|               class="btn-sm" | ||||
|             > | ||||
|               {{ $t('dashboard.recent_estimate_card.view_all') }} | ||||
|             </base-button> | ||||
|           </router-link> | ||||
|         </div> | ||||
|         <div class="dashboard-table"> | ||||
|           <table-component | ||||
|             ref="table" | ||||
|             :data="getRecentEstimates" | ||||
|             :show-filter="false" | ||||
|             table-class="table" | ||||
|           > | ||||
|             <table-column :label="$t('dashboard.recent_estimate_card.date')" show="formattedExpiryDate" /> | ||||
|             <table-column :label="$t('dashboard.recent_estimate_card.customer')" show="user.name" /> | ||||
|             <table-column :label="$t('dashboard.recent_estimate_card.amount_due')" sort-as="total"> | ||||
|               <template slot-scope="row"> | ||||
|                 <span>{{ $t('dashboard.recent_estimate_card.amount_due') }}</span> | ||||
|                 <div v-html="$utils.formatMoney(row.total, row.user.currency)"/> | ||||
|               </template> | ||||
|             </table-column> | ||||
|             <table-column | ||||
|               :sortable="false" | ||||
|               :filterable="false" | ||||
|               cell-class="action-dropdown no-click" | ||||
|             > | ||||
|               <template slot-scope="row"> | ||||
|                 <v-dropdown> | ||||
|                   <a slot="activator" href="#"> | ||||
|                     <dot-icon /> | ||||
|                   </a> | ||||
|                   <v-dropdown-item> | ||||
|                     <router-link :to="{path: `estimates/${row.id}/edit`}" class="dropdown-item"> | ||||
|                       <font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon" /> | ||||
|                       {{ $t('general.edit') }} | ||||
|                     </router-link> | ||||
|                   </v-dropdown-item> | ||||
|                   <v-dropdown-item> | ||||
|                     <div class="dropdown-item" @click="removeEstimate(row.id)"> | ||||
|                       <font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" /> | ||||
|                       {{ $t('general.delete') }} | ||||
|                     </div> | ||||
|                   </v-dropdown-item> | ||||
|                   <v-dropdown-item> | ||||
|                     <router-link :to="{path: `estimates/${row.id}/view`}" class="dropdown-item"> | ||||
|                       <font-awesome-icon icon="eye" class="dropdown-item-icon" /> | ||||
|                       {{ $t('general.view') }} | ||||
|                     </router-link> | ||||
|                   </v-dropdown-item> | ||||
|                   <v-dropdown-item> | ||||
|                     <a class="dropdown-item" href="#" @click="convertInToinvoice(row.id)"> | ||||
|                       <font-awesome-icon icon="envelope" class="dropdown-item-icon" /> | ||||
|                       {{ $t('estimates.convert_to_invoice') }} | ||||
|                     </a> | ||||
|                   </v-dropdown-item> | ||||
|                   <v-dropdown-item v-if="row.status === 'DRAFT'"> | ||||
|                     <a class="dropdown-item" href="#" @click.self="onMarkAsSent(row.id)"> | ||||
|                       <font-awesome-icon icon="check-circle" class="dropdown-item-icon" /> | ||||
|                       {{ $t('estimates.mark_as_sent') }} | ||||
|                     </a> | ||||
|                   </v-dropdown-item> | ||||
|                 </v-dropdown> | ||||
|               </template> | ||||
|             </table-column> | ||||
|           </table-component> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapGetters, mapActions } from 'vuex' | ||||
| import { SweetModal, SweetModalTab } from 'sweet-modal-vue' | ||||
| import LineChart from '../../components/chartjs/LineChart' | ||||
| import DollarIcon from '../../components/icon/DollarIcon' | ||||
| import ContactIcon from '../../components/icon/ContactIcon' | ||||
| import InvoiceIcon from '../../components/icon/InvoiceIcon' | ||||
| import EstimateIcon from '../../components/icon/EstimateIcon' | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     LineChart, | ||||
|     DollarIcon, | ||||
|     ContactIcon, | ||||
|     InvoiceIcon, | ||||
|     EstimateIcon | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       incomeTotal: null, | ||||
|       ...this.$store.state.dashboard, | ||||
|       currency: { precision: 2, thousand_separator: ',', decimal_separator: '.', symbol: '$' }, | ||||
|       isLoaded: false, | ||||
|       fetching: false, | ||||
|       years: ['This year', 'Previous year'], | ||||
|       selectedYear: 'This year' | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('user', { | ||||
|       'user': 'currentUser' | ||||
|     }), | ||||
|     ...mapGetters('dashboard', [ | ||||
|       'getChartMonths', | ||||
|       'getChartInvoices', | ||||
|       'getChartExpenses', | ||||
|       'getNetProfits', | ||||
|       'getReceiptTotals', | ||||
|       'getContacts', | ||||
|       'getInvoices', | ||||
|       'getEstimates', | ||||
|       'getTotalDueAmount', | ||||
|       'getTotalSales', | ||||
|       'getTotalReceipts', | ||||
|       'getTotalExpenses', | ||||
|       'getNetProfit', | ||||
|       'getLoadedData', | ||||
|       'getDueInvoices', | ||||
|       'getRecentEstimates' | ||||
|     ]), | ||||
|     ...mapGetters('currency', [ | ||||
|       'defaultCurrency' | ||||
|     ]) | ||||
|   }, | ||||
|   watch: { | ||||
|     selectedYear (val) { | ||||
|       if (val === 'Previous year') { | ||||
|         let params = {previous_year: true} | ||||
|         this.loadData(params) | ||||
|       } else { | ||||
|         this.loadData() | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     this.loadChart() | ||||
|     this.loadData() | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('dashboard', [ | ||||
|       'getChart', | ||||
|       'loadData' | ||||
|     ]), | ||||
|     ...mapActions('estimate', [ | ||||
|       'deleteEstimate', | ||||
|       'convertToInvoice' | ||||
|     ]), | ||||
|     ...mapActions('invoice', [ | ||||
|       'deleteInvoice', | ||||
|       'sendEmail', | ||||
|       'markAsSent' | ||||
|     ]), | ||||
|  | ||||
|     async loadChart () { | ||||
|       await this.$store.dispatch('dashboard/getChart') | ||||
|       this.isLoaded = true | ||||
|     }, | ||||
|  | ||||
|     async loadData (params) { | ||||
|       await this.$store.dispatch('dashboard/loadData', params) | ||||
|       this.isLoaded = true | ||||
|     }, | ||||
|  | ||||
|     async removeEstimate (id) { | ||||
|       this.id = id | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$tc('estimates.confirm_delete', 1), | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let res = await this.deleteEstimate(this.id) | ||||
|           if (res.data.success) { | ||||
|             window.toastr['success'](this.$tc('estimates.deleted_message', 1)) | ||||
|             this.$refs.table.refresh() | ||||
|           } else if (res.data.error) { | ||||
|             window.toastr['error'](res.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|  | ||||
|     async convertInToinvoice (id) { | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$t('estimates.confirm_conversion'), | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let res = await this.convertToInvoice(id) | ||||
|           this.selectAllField = false | ||||
|           if (res.data) { | ||||
|             window.toastr['success'](this.$t('estimates.conversion_message')) | ||||
|             this.$router.push(`invoices/${res.data.invoice.id}/edit`) | ||||
|           } else if (res.data.error) { | ||||
|             window.toastr['error'](res.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     async onMarkAsSent (id) { | ||||
|       const data = { | ||||
|         id: id | ||||
|       } | ||||
|       let response = await this.markAsSent(data) | ||||
|       this.$refs.table.refresh() | ||||
|       if (response.data) { | ||||
|         window.toastr['success'](this.$tc('estimates.mark_as_sent')) | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     async removeInvoice (id) { | ||||
|       this.id = id | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$tc('invoices.confirm_delete'), | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let res = await this.deleteInvoice(this.id) | ||||
|           if (res.data.success) { | ||||
|             window.toastr['success'](this.$tc('invoices.deleted_message')) | ||||
|             this.$refs.table.refresh() | ||||
|           } else if (res.data.error) { | ||||
|             window.toastr['error'](res.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|  | ||||
|     async sendInvoice (id) { | ||||
|       const data = { | ||||
|         id: id | ||||
|       } | ||||
|       let response = await this.sendEmail(data) | ||||
|       this.$refs.table.refresh() | ||||
|       if (response.data) { | ||||
|         window.toastr['success'](this.$tc('invoices.send_invoice')) | ||||
|       } | ||||
|     }, | ||||
|     async sentInvoice (id) { | ||||
|       const data = { | ||||
|         id: id | ||||
|       } | ||||
|       let response = await this.markAsSent(data) | ||||
|       this.$refs.table.refresh() | ||||
|       if (response.data) { | ||||
|         window.toastr['success'](this.$tc('invoices.mark_as_sent')) | ||||
|       } | ||||
|     } | ||||
|  | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										32
									
								
								resources/assets/js/views/errors/404.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								resources/assets/js/views/errors/404.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| <template> | ||||
|   <div class="error-box"> | ||||
|     <div class="row"> | ||||
|       <div class="col-sm-12 text-sm-center"> | ||||
|         <h1>{{ $t('general.four_zero_four') }}</h1> | ||||
|         <h5>{{ $t('general.yot_got_lost') }}</h5> | ||||
|         <router-link | ||||
|           class="btn btn-lg bg-yellow text-white" | ||||
|           to="/"> | ||||
|           <font-awesome-icon icon="arrow-left" class="icon text-white mr-2"/> {{ $t('general.go_home') }} | ||||
|         </router-link> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|   mounted () { | ||||
|     this.setLayoutClasses() | ||||
|   }, | ||||
|   destroyed () { | ||||
|     $('body').removeClass('page-error-404') | ||||
|   }, | ||||
|   methods: { | ||||
|     setLayoutClasses () { | ||||
|       let body = $('body') | ||||
|       body.addClass('page-error-404') | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										698
									
								
								resources/assets/js/views/estimates/Create.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										698
									
								
								resources/assets/js/views/estimates/Create.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,698 @@ | ||||
| <template> | ||||
|   <div class="estimate-create-page main-content"> | ||||
|     <form v-if="!initLoading" action="" @submit.prevent="submitEstimateData"> | ||||
|       <div class="page-header"> | ||||
|         <h3 v-if="$route.name === 'estimates.edit'" class="page-title">{{ $t('estimates.edit_estimate') }}</h3> | ||||
|         <h3 v-else class="page-title">{{ $t('estimates.new_estimate') }}</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/estimates">{{ $tc('estimates.estimate', 2) }}</router-link></li> | ||||
|           <li v-if="$route.name === 'estimates.edit'" class="breadcrumb-item">{{ $t('estimates.edit_estimate') }}</li> | ||||
|           <li v-else class="breadcrumb-item">{{ $t('estimates.new_estimate') }}</li> | ||||
|         </ol> | ||||
|         <div class="page-actions row"> | ||||
|           <a v-if="$route.name === 'estimates.edit'" :href="`/estimates/pdf/${newEstimate.unique_hash}`" target="_blank" class="mr-3 base-button btn btn-outline-primary default-size" outline color="theme"> | ||||
|             {{ $t('general.view_pdf') }} | ||||
|           </a> | ||||
|           <base-button | ||||
|             :loading="isLoading" | ||||
|             :disabled="isLoading" | ||||
|             icon="save" | ||||
|             color="theme" | ||||
|             type="submit"> | ||||
|             {{ $t('estimates.save_estimate') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row estimate-input-group"> | ||||
|         <div class="col-md-5"> | ||||
|           <div | ||||
|             v-if="selectedCustomer" | ||||
|             class="show-customer" | ||||
|           > | ||||
|             <div class="row px-2 mt-1"> | ||||
|               <div class="col col-6"> | ||||
|                 <div v-if="selectedCustomer.billing_address != null" class="row address-menu"> | ||||
|                   <label class="col-sm-4 px-2 title">{{ $t('general.bill_to') }}</label> | ||||
|                   <div class="col-sm p-0 px-2 content"> | ||||
|                     <label v-if="selectedCustomer.billing_address.name"> | ||||
|                       {{ selectedCustomer.billing_address.name }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.billing_address.address_street_1"> | ||||
|                       {{ selectedCustomer.billing_address.address_street_1 }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.billing_address.address_street_2"> | ||||
|                       {{ selectedCustomer.billing_address.address_street_2 }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.billing_address.city && selectedCustomer.billing_address.state"> | ||||
|                       {{ selectedCustomer.billing_address.city.name }}, {{ selectedCustomer.billing_address.state.name }} {{ selectedCustomer.billing_address.zip }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.billing_address.country"> | ||||
|                       {{ selectedCustomer.billing_address.country.name }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.billing_address.phone"> | ||||
|                       {{ selectedCustomer.billing_address.phone }} | ||||
|                     </label> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="col col-6"> | ||||
|                 <div v-if="selectedCustomer.shipping_address != null" class="row address-menu"> | ||||
|                   <label class="col-sm-4 px-2 title">{{ $t('general.ship_to') }}</label> | ||||
|                   <div class="col-sm p-0 px-2 content"> | ||||
|                     <label v-if="selectedCustomer.shipping_address.name"> | ||||
|                       {{ selectedCustomer.shipping_address.name }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.shipping_address.address_street_1"> | ||||
|                       {{ selectedCustomer.shipping_address.address_street_1 }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.shipping_address.address_street_2"> | ||||
|                       {{ selectedCustomer.shipping_address.address_street_2 }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.shipping_address.city && selectedCustomer.shipping_address"> | ||||
|                       {{ selectedCustomer.shipping_address.city.name }}, {{ selectedCustomer.shipping_address.state.name }} {{ selectedCustomer.shipping_address.zip }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.shipping_address.country" class="country"> | ||||
|                       {{ selectedCustomer.shipping_address.country.name }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.shipping_address.phone" class="phone"> | ||||
|                       {{ selectedCustomer.shipping_address.phone }} | ||||
|                     </label> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="customer-content mb-1"> | ||||
|               <label class="email">{{ selectedCustomer.email ? selectedCustomer.email : selectedCustomer.name }}</label> | ||||
|               <label class="action" @click="removeCustomer">{{ $t('general.remove') }}</label> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
|           <base-popup v-else :class="['add-customer', {'customer-required': $v.selectedCustomer.$error}]" > | ||||
|             <div slot="activator" class="add-customer-action"> | ||||
|               <font-awesome-icon icon="user" class="customer-icon"/> | ||||
|               <div> | ||||
|                 <label>{{ $t('customers.new_customer') }} <span class="text-danger"> * </span></label> | ||||
|                 <p v-if="$v.selectedCustomer.$error && !$v.selectedCustomer.required" class="text-danger"> {{ $t('estimates.errors.required') }} </p> | ||||
|               </div> | ||||
|             </div> | ||||
|             <customer-select-popup type="estimate" /> | ||||
|           </base-popup> | ||||
|         </div> | ||||
|         <div class="col estimate-input"> | ||||
|           <div class="row mb-3"> | ||||
|             <div class="col"> | ||||
|               <label>{{ $t('reports.estimates.estimate_date') }}<span class="text-danger"> * </span></label> | ||||
|               <base-date-picker | ||||
|                 v-model="newEstimate.estimate_date" | ||||
|                 :calendar-button="true" | ||||
|                 calendar-button-icon="calendar" | ||||
|                 @change="$v.newEstimate.estimate_date.$touch()" | ||||
|               /> | ||||
|               <span v-if="$v.newEstimate.estimate_date.$error && !$v.newEstimate.estimate_date.required" class="text-danger"> {{ $t('validation.required') }} </span> | ||||
|             </div> | ||||
|             <div class="col"> | ||||
|               <label>{{ $t('estimates.due_date') }}<span class="text-danger"> * </span></label> | ||||
|               <base-date-picker | ||||
|                 v-model="newEstimate.expiry_date" | ||||
|                 :invalid="$v.newEstimate.expiry_date.$error" | ||||
|                 :calendar-button="true" | ||||
|                 calendar-button-icon="calendar" | ||||
|                 @change="$v.newEstimate.expiry_date.$touch()" | ||||
|               /> | ||||
|               <span v-if="$v.newEstimate.expiry_date.$error && !$v.newEstimate.expiry_date.required" class="text-danger mt-1"> {{ $t('validation.required') }}</span> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="row mt-4"> | ||||
|             <div class="col"> | ||||
|               <label>{{ $t('estimates.estimate_number') }}<span class="text-danger"> * </span></label> | ||||
|               <base-input | ||||
|                 :invalid="$v.newEstimate.estimate_number.$error" | ||||
|                 :read-only="true" | ||||
|                 v-model="newEstimate.estimate_number" | ||||
|                 icon="hashtag" | ||||
|                 @input="$v.newEstimate.estimate_number.$touch()" | ||||
|               /> | ||||
|               <span v-show="$v.newEstimate.estimate_number.$error && !$v.newEstimate.estimate_number.required" class="text-danger mt-1"> {{ $tc('estimates.errors.required') }}  </span> | ||||
|             </div> | ||||
|             <div class="col"> | ||||
|               <label>{{ $t('estimates.ref_number') }}</label> | ||||
|               <base-input | ||||
|                 v-model="newEstimate.reference_number" | ||||
|                 :invalid="$v.newEstimate.reference_number.$error" | ||||
|                 icon="hashtag" | ||||
|                 type="number" | ||||
|                 @input="$v.newEstimate.reference_number.$touch()" | ||||
|               /> | ||||
|               <div v-if="$v.newEstimate.reference_number.$error" class="text-danger">{{ $tc('validation.ref_number_maxlength') }}</div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <table class="item-table"> | ||||
|         <colgroup> | ||||
|           <col style="width: 40%;"> | ||||
|           <col style="width: 10%;"> | ||||
|           <col style="width: 15%;"> | ||||
|           <col v-if="discountPerItem === 'YES'" style="width: 15%;"> | ||||
|           <col style="width: 15%;"> | ||||
|         </colgroup> | ||||
|         <thead class="item-table-header"> | ||||
|           <tr> | ||||
|             <th class="text-left"> | ||||
|               <span class="column-heading item-heading"> | ||||
|                 {{ $tc('items.item',2) }} | ||||
|               </span> | ||||
|             </th> | ||||
|             <th class="text-right"> | ||||
|               <span class="column-heading"> | ||||
|                 {{ $t('estimates.item.quantity') }} | ||||
|               </span> | ||||
|             </th> | ||||
|             <th class="text-left"> | ||||
|               <span class="column-heading"> | ||||
|                 {{ $t('estimates.item.price') }} | ||||
|               </span> | ||||
|             </th> | ||||
|             <th v-if="discountPerItem === 'YES'" class="text-right"> | ||||
|               <span class="column-heading"> | ||||
|                 {{ $t('estimates.item.discount') }} | ||||
|               </span> | ||||
|             </th> | ||||
|             <th class="text-right"> | ||||
|               <span class="column-heading amount-heading"> | ||||
|                 {{ $t('estimates.item.amount') }} | ||||
|               </span> | ||||
|             </th> | ||||
|           </tr> | ||||
|         </thead> | ||||
|         <draggable v-model="newEstimate.items" class="item-body" tag="tbody" handle=".handle"> | ||||
|           <estimate-item | ||||
|             v-for="(item, index) in newEstimate.items" | ||||
|             :key="item.id" | ||||
|             :index="index" | ||||
|             :item-data="item" | ||||
|             :currency="currency" | ||||
|             :tax-per-item="taxPerItem" | ||||
|             :discount-per-item="discountPerItem" | ||||
|             @remove="removeItem" | ||||
|             @update="updateItem" | ||||
|             @itemValidate="checkItemsData" | ||||
|           /> | ||||
|         </draggable> | ||||
|       </table> | ||||
|       <div class="add-item-action" @click="addItem"> | ||||
|         <font-awesome-icon icon="shopping-basket" class="mr-2"/> | ||||
|         {{ $t('estimates.add_item') }} | ||||
|       </div> | ||||
|  | ||||
|       <div class="estimate-foot"> | ||||
|         <div> | ||||
|           <label>{{ $t('estimates.notes') }}</label> | ||||
|           <base-text-area | ||||
|             v-model="newEstimate.notes" | ||||
|             rows="3" | ||||
|             cols="50" | ||||
|             @input="$v.newEstimate.notes.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.newEstimate.notes.$error"> | ||||
|             <span v-if="!$v.newEstimate.notes.maxLength" class="text-danger">{{ $t('validation.notes_maxlength') }}</span> | ||||
|           </div> | ||||
|           <label class="mt-3 mb-1 d-block">{{ $t('estimates.estimate_template') }} <span class="text-danger"> * </span></label> | ||||
|           <base-button type="button" class="btn-template" icon="pencil-alt" right-icon @click="openTemplateModal" > | ||||
|             <span class="mr-4"> {{ $t('estimates.estimate_template') }} {{ getTemplateId }} </span> | ||||
|           </base-button> | ||||
|         </div> | ||||
|  | ||||
|         <div class="estimate-total"> | ||||
|           <div class="section"> | ||||
|             <label class="estimate-label">{{ $t('estimates.sub_total') }}</label> | ||||
|             <label class="estimate-amount"> | ||||
|               <div v-html="$utils.formatMoney(subtotal, currency)" /> | ||||
|             </label> | ||||
|           </div> | ||||
|           <div v-for="tax in allTaxes" :key="tax.tax_type_id" class="section"> | ||||
|             <label class="estimate-label">{{ tax.name }} - {{ tax.percent }}% </label> | ||||
|             <label class="estimate-amount"> | ||||
|               <div v-html="$utils.formatMoney(tax.amount, currency)" /> | ||||
|             </label> | ||||
|           </div> | ||||
|           <div v-if="discountPerItem === 'NO' || discountPerItem === null" class="section mt-2"> | ||||
|             <label class="estimate-label">{{ $t('estimates.discount') }}</label> | ||||
|             <div | ||||
|               class="btn-group discount-drop-down" | ||||
|               role="group" | ||||
|             > | ||||
|               <base-input | ||||
|                 v-model="discount" | ||||
|                 :invalid="$v.newEstimate.discount_val.$error" | ||||
|                 input-class="item-discount" | ||||
|                 @input="$v.newEstimate.discount_val.$touch()" | ||||
|               /> | ||||
|               <v-dropdown :show-arrow="false"> | ||||
|                 <button | ||||
|                   slot="activator" | ||||
|                   type="button" | ||||
|                   class="btn item-dropdown dropdown-toggle" | ||||
|                   data-toggle="dropdown" | ||||
|                   aria-haspopup="true" | ||||
|                   aria-expanded="false" | ||||
|                 > | ||||
|                   {{ newEstimate.discount_type == 'fixed' ? currency.symbol : '%' }} | ||||
|                 </button> | ||||
|                 <v-dropdown-item> | ||||
|                   <a class="dropdown-item" href="#" @click.prevent="selectFixed"> | ||||
|                     {{ $t('general.fixed') }} | ||||
|                   </a> | ||||
|                 </v-dropdown-item> | ||||
|                 <v-dropdown-item> | ||||
|                   <a class="dropdown-item" href="#" @click.prevent="selectPercentage"> | ||||
|                     {{ $t('general.percentage') }} | ||||
|                   </a> | ||||
|                 </v-dropdown-item> | ||||
|               </v-dropdown> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
|           <div v-if="taxPerItem === 'NO' || taxPerItem === null"> | ||||
|             <tax | ||||
|               v-for="(tax, index) in newEstimate.taxes" | ||||
|               :index="index" | ||||
|               :total="subtotalWithDiscount" | ||||
|               :key="tax.id" | ||||
|               :tax="tax" | ||||
|               :taxes="newEstimate.taxes" | ||||
|               :currency="currency" | ||||
|               :total-tax="totalSimpleTax" | ||||
|               @remove="removeEstimateTax" | ||||
|               @update="updateTax" | ||||
|             /> | ||||
|           </div> | ||||
|  | ||||
|           <base-popup v-if="taxPerItem === 'NO' || taxPerItem === null" ref="taxModal" class="tax-selector"> | ||||
|             <div slot="activator" class="float-right"> | ||||
|               + {{ $t('estimates.add_tax') }} | ||||
|             </div> | ||||
|             <tax-select-popup :taxes="newEstimate.taxes" @select="onSelectTax" /> | ||||
|           </base-popup> | ||||
|  | ||||
|           <div class="section border-top mt-3"> | ||||
|             <label class="estimate-label">{{ $t('estimates.total') }} {{ $t('estimates.amount') }}:</label> | ||||
|             <label class="estimate-amount total"> | ||||
|               <div v-html="$utils.formatMoney(total, currency)" /> | ||||
|             </label> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </form> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import draggable from 'vuedraggable' | ||||
| import MultiSelect from 'vue-multiselect' | ||||
| import EstimateItem from './Item' | ||||
| import EstimateStub from '../../stub/estimate' | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
| import moment from 'moment' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import Guid from 'guid' | ||||
| import TaxStub from '../../stub/tax' | ||||
| import Tax from './EstimateTax' | ||||
| const { required, between, maxLength } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     EstimateItem, | ||||
|     MultiSelect, | ||||
|     Tax, | ||||
|     draggable | ||||
|   }, | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       newEstimate: { | ||||
|         estimate_date: null, | ||||
|         expiry_date: null, | ||||
|         estimate_number: null, | ||||
|         user_id: null, | ||||
|         estimate_template_id: 1, | ||||
|         sub_total: null, | ||||
|         total: null, | ||||
|         tax: null, | ||||
|         notes: null, | ||||
|         discount_type: 'fixed', | ||||
|         discount_val: 0, | ||||
|         reference_number: null, | ||||
|         discount: 0, | ||||
|         items: [{ | ||||
|           ...EstimateStub, | ||||
|           id: Guid.raw(), | ||||
|           taxes: [{...TaxStub, id: Guid.raw()}] | ||||
|         }], | ||||
|         taxes: [] | ||||
|       }, | ||||
|       customers: [], | ||||
|       itemList: [], | ||||
|       estimateTemplates: [], | ||||
|       selectedCurrency: '', | ||||
|       taxPerItem: null, | ||||
|       discountPerItem: null, | ||||
|       initLoading: false, | ||||
|       isLoading: false, | ||||
|       maxDiscount: 0 | ||||
|     } | ||||
|   }, | ||||
|   validations () { | ||||
|     return { | ||||
|       newEstimate: { | ||||
|         estimate_date: { | ||||
|           required | ||||
|         }, | ||||
|         expiry_date: { | ||||
|           required | ||||
|         }, | ||||
|         estimate_number: { | ||||
|           required | ||||
|         }, | ||||
|         discount_val: { | ||||
|           between: between(0, this.subtotal) | ||||
|         }, | ||||
|         notes: { | ||||
|           maxLength: maxLength(255) | ||||
|         }, | ||||
|         reference_number: { | ||||
|           maxLength: maxLength(10) | ||||
|         } | ||||
|       }, | ||||
|       selectedCustomer: { | ||||
|         required | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('general', [ | ||||
|       'itemDiscount' | ||||
|     ]), | ||||
|     ...mapGetters('currency', [ | ||||
|       'defaultCurrency' | ||||
|     ]), | ||||
|     ...mapGetters('estimate', [ | ||||
|       'getTemplateId', | ||||
|       'selectedCustomer' | ||||
|     ]), | ||||
|     currency () { | ||||
|       return this.selectedCurrency | ||||
|     }, | ||||
|     subtotalWithDiscount () { | ||||
|       return this.subtotal - this.newEstimate.discount_val | ||||
|     }, | ||||
|     total () { | ||||
|       return this.subtotalWithDiscount + this.totalTax | ||||
|     }, | ||||
|     subtotal () { | ||||
|       return this.newEstimate.items.reduce(function (a, b) { | ||||
|         return a + b['total'] | ||||
|       }, 0) | ||||
|     }, | ||||
|     discount: { | ||||
|       get: function () { | ||||
|         return this.newEstimate.discount | ||||
|       }, | ||||
|       set: function (newValue) { | ||||
|         if (this.newEstimate.discount_type === 'percentage') { | ||||
|           this.newEstimate.discount_val = (this.subtotal * newValue) / 100 | ||||
|         } else { | ||||
|           this.newEstimate.discount_val = newValue * 100 | ||||
|         } | ||||
|  | ||||
|         this.newEstimate.discount = newValue | ||||
|       } | ||||
|     }, | ||||
|     totalSimpleTax () { | ||||
|       return window._.sumBy(this.newEstimate.taxes, function (tax) { | ||||
|         if (!tax.compound_tax) { | ||||
|           return tax.amount | ||||
|         } | ||||
|  | ||||
|         return 0 | ||||
|       }) | ||||
|     }, | ||||
|  | ||||
|     totalCompoundTax () { | ||||
|       return window._.sumBy(this.newEstimate.taxes, function (tax) { | ||||
|         if (tax.compound_tax) { | ||||
|           return tax.amount | ||||
|         } | ||||
|  | ||||
|         return 0 | ||||
|       }) | ||||
|     }, | ||||
|     totalTax () { | ||||
|       if (this.taxPerItem === 'NO' || this.taxPerItem === null) { | ||||
|         return this.totalSimpleTax + this.totalCompoundTax | ||||
|       } | ||||
|  | ||||
|       return window._.sumBy(this.newEstimate.items, function (tax) { | ||||
|         return tax.tax | ||||
|       }) | ||||
|     }, | ||||
|     allTaxes () { | ||||
|       let taxes = [] | ||||
|  | ||||
|       this.newEstimate.items.forEach((item) => { | ||||
|         item.taxes.forEach((tax) => { | ||||
|           let found = taxes.find((_tax) => { | ||||
|             return _tax.tax_type_id === tax.tax_type_id | ||||
|           }) | ||||
|  | ||||
|           if (found) { | ||||
|             found.amount += tax.amount | ||||
|           } else if (tax.tax_type_id) { | ||||
|             taxes.push({ | ||||
|               tax_type_id: tax.tax_type_id, | ||||
|               amount: tax.amount, | ||||
|               percent: tax.percent, | ||||
|               name: tax.name | ||||
|             }) | ||||
|           } | ||||
|         }) | ||||
|       }) | ||||
|  | ||||
|       return taxes | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     selectedCustomer (newVal) { | ||||
|       if (newVal && newVal.currency) { | ||||
|         this.selectedCurrency = newVal.currency | ||||
|       } else { | ||||
|         this.selectedCurrency = this.defaultCurrency | ||||
|       } | ||||
|     }, | ||||
|     subtotal (newValue) { | ||||
|       if (this.newEstimate.discount_type === 'percentage') { | ||||
|         this.newEstimate.discount_val = (this.newEstimate.discount * newValue) / 100 | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     this.loadData() | ||||
|     this.fetchInitialItems() | ||||
|     this.resetSelectedCustomer() | ||||
|     window.hub.$on('newTax', this.onSelectTax) | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('modal', [ | ||||
|       'openModal' | ||||
|     ]), | ||||
|     ...mapActions('estimate', [ | ||||
|       'addEstimate', | ||||
|       'fetchCreateEstimate', | ||||
|       'fetchEstimate', | ||||
|       'resetSelectedCustomer', | ||||
|       'selectCustomer', | ||||
|       'updateEstimate' | ||||
|     ]), | ||||
|     ...mapActions('item', [ | ||||
|       'fetchItems' | ||||
|     ]), | ||||
|     selectFixed () { | ||||
|       if (this.newEstimate.discount_type === 'fixed') { | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       this.newEstimate.discount_val = this.newEstimate.discount * 100 | ||||
|       this.newEstimate.discount_type = 'fixed' | ||||
|     }, | ||||
|     selectPercentage () { | ||||
|       if (this.newEstimate.discount_type === 'percentage') { | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       this.newEstimate.discount_val = (this.subtotal * this.newEstimate.discount) / 100 | ||||
|  | ||||
|       this.newEstimate.discount_type = 'percentage' | ||||
|     }, | ||||
|     updateTax (data) { | ||||
|       Object.assign(this.newEstimate.taxes[data.index], {...data.item}) | ||||
|     }, | ||||
|     async fetchInitialItems () { | ||||
|       await this.fetchItems({ | ||||
|         filter: {}, | ||||
|         orderByField: '', | ||||
|         orderBy: '' | ||||
|       }) | ||||
|     }, | ||||
|     async loadData () { | ||||
|       if (this.$route.name === 'estimates.edit') { | ||||
|         this.initLoading = true | ||||
|         let response = await this.fetchEstimate(this.$route.params.id) | ||||
|  | ||||
|         if (response.data) { | ||||
|           this.selectCustomer(response.data.estimate.user_id) | ||||
|           this.newEstimate = response.data.estimate | ||||
|           this.discountPerItem = response.data.discount_per_item | ||||
|           this.taxPerItem = response.data.tax_per_item | ||||
|           this.selectedCurrency = this.defaultCurrency | ||||
|           this.estimateTemplates = response.data.estimateTemplates | ||||
|         } | ||||
|         this.initLoading = false | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       this.initLoading = true | ||||
|       let response = await this.fetchCreateEstimate() | ||||
|       if (response.data) { | ||||
|         this.discountPerItem = response.data.discount_per_item | ||||
|         this.taxPerItem = response.data.tax_per_item | ||||
|         this.selectedCurrency = this.defaultCurrency | ||||
|         this.estimateTemplates = response.data.estimateTemplates | ||||
|         let today = new Date() | ||||
|         this.newEstimate.estimate_date = moment(today).toString() | ||||
|         this.newEstimate.expiry_date = moment(today).add(7, 'days').toString() | ||||
|         this.newEstimate.estimate_number = response.data.nextEstimateNumber | ||||
|         this.itemList = response.data.items | ||||
|       } | ||||
|       this.initLoading = false | ||||
|     }, | ||||
|     removeCustomer () { | ||||
|       this.resetSelectedCustomer() | ||||
|     }, | ||||
|     openTemplateModal () { | ||||
|       this.openModal({ | ||||
|         'title': 'Choose a template', | ||||
|         'componentName': 'EstimateTemplate', | ||||
|         'data': this.estimateTemplates | ||||
|       }) | ||||
|     }, | ||||
|     addItem () { | ||||
|       this.newEstimate.items.push({...EstimateStub, id: Guid.raw(), taxes: [{...TaxStub, id: Guid.raw()}]}) | ||||
|     }, | ||||
|     removeItem (index) { | ||||
|       this.newEstimate.items.splice(index, 1) | ||||
|     }, | ||||
|     updateItem (data) { | ||||
|       Object.assign(this.newEstimate.items[data.index], {...data.item}) | ||||
|     }, | ||||
|     submitEstimateData () { | ||||
|       if (!this.checkValid()) { | ||||
|         return false | ||||
|       } | ||||
|  | ||||
|       this.isLoading = true | ||||
|  | ||||
|       let data = { | ||||
|         ...this.newEstimate, | ||||
|         estimate_date: moment(this.newEstimate.estimate_date).format('DD/MM/YYYY'), | ||||
|         expiry_date: moment(this.newEstimate.expiry_date).format('DD/MM/YYYY'), | ||||
|         sub_total: this.subtotal, | ||||
|         total: this.total, | ||||
|         tax: this.totalTax, | ||||
|         user_id: null, | ||||
|         estimate_template_id: this.getTemplateId | ||||
|       } | ||||
|  | ||||
|       if (this.selectedCustomer != null) { | ||||
|         data.user_id = this.selectedCustomer.id | ||||
|       } | ||||
|  | ||||
|       if (this.$route.name === 'estimates.edit') { | ||||
|         this.submitUpdate(data) | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       this.submitSave(data) | ||||
|     }, | ||||
|     submitSave (data) { | ||||
|       this.addEstimate(data).then((res) => { | ||||
|         if (res.data) { | ||||
|           window.toastr['success'](this.$t('estimates.created_message')) | ||||
|           this.$router.push('/admin/estimates') | ||||
|         } | ||||
|  | ||||
|         this.isLoading = false | ||||
|       }).catch((err) => { | ||||
|         this.isLoading = false | ||||
|         console.log(err) | ||||
|       }) | ||||
|     }, | ||||
|     submitUpdate (data) { | ||||
|       this.updateEstimate(data).then((res) => { | ||||
|         if (res.data) { | ||||
|           window.toastr['success'](this.$t('estimates.updated_message')) | ||||
|           this.$router.push('/admin/estimates') | ||||
|         } | ||||
|  | ||||
|         this.isLoading = false | ||||
|       }).catch((err) => { | ||||
|         this.isLoading = false | ||||
|         console.log(err) | ||||
|       }) | ||||
|     }, | ||||
|     checkItemsData (index, isValid) { | ||||
|       this.newEstimate.items[index].valid = isValid | ||||
|     }, | ||||
|     onSelectTax (selectedTax) { | ||||
|       let amount = 0 | ||||
|  | ||||
|       if (selectedTax.compound_tax && this.subtotalWithDiscount) { | ||||
|         amount = ((this.subtotalWithDiscount + this.totalSimpleTax) * selectedTax.percent) / 100 | ||||
|       } else if (this.subtotalWithDiscount && selectedTax.percent) { | ||||
|         amount = (this.subtotalWithDiscount * selectedTax.percent) / 100 | ||||
|       } | ||||
|  | ||||
|       this.newEstimate.taxes.push({ | ||||
|         ...TaxStub, | ||||
|         id: Guid.raw(), | ||||
|         name: selectedTax.name, | ||||
|         percent: selectedTax.percent, | ||||
|         compound_tax: selectedTax.compound_tax, | ||||
|         tax_type_id: selectedTax.id, | ||||
|         amount | ||||
|       }) | ||||
|  | ||||
|       this.$refs.taxModal.close() | ||||
|     }, | ||||
|     removeEstimateTax (index) { | ||||
|       this.newEstimate.taxes.splice(index, 1) | ||||
|     }, | ||||
|     checkValid () { | ||||
|       this.$v.newEstimate.$touch() | ||||
|       this.$v.selectedCustomer.$touch() | ||||
|       window.hub.$emit('checkItems') | ||||
|       let isValid = true | ||||
|       this.newEstimate.items.forEach((item) => { | ||||
|         if (!item.valid) { | ||||
|           isValid = false | ||||
|         } | ||||
|       }) | ||||
|       if (this.$v.newEstimate.$invalid === false && isValid === true) { | ||||
|         return true | ||||
|       } | ||||
|       return false | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										83
									
								
								resources/assets/js/views/estimates/EstimateTax.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								resources/assets/js/views/estimates/EstimateTax.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | ||||
| <template> | ||||
|   <div class="section mt-2"> | ||||
|     <label class="estimate-label"> | ||||
|       {{ tax.name }} ({{ tax.percent }}%) | ||||
|     </label> | ||||
|     <label class="estimate-amount"> | ||||
|       <div v-html="$utils.formatMoney(tax.amount, currency)" /> | ||||
|  | ||||
|       <font-awesome-icon | ||||
|         class="ml-2" | ||||
|         icon="trash-alt" | ||||
|         @click="$emit('remove', index)" | ||||
|       /> | ||||
|     </label> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|   props: { | ||||
|     index: { | ||||
|       type: Number, | ||||
|       required: true | ||||
|     }, | ||||
|     tax: { | ||||
|       type: Object, | ||||
|       required: true | ||||
|     }, | ||||
|     taxes: { | ||||
|       type: Array, | ||||
|       required: true | ||||
|     }, | ||||
|     total: { | ||||
|       type: Number, | ||||
|       default: 0 | ||||
|     }, | ||||
|     totalTax: { | ||||
|       type: Number, | ||||
|       default: 0 | ||||
|     }, | ||||
|     currency: { | ||||
|       type: [Object, String], | ||||
|       required: true | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     taxAmount () { | ||||
|       if (this.tax.compound_tax && this.total) { | ||||
|         return ((this.total + this.totalTax) * this.tax.percent) / 100 | ||||
|       } | ||||
|  | ||||
|       if (this.total && this.tax.percent) { | ||||
|         return (this.total * this.tax.percent) / 100 | ||||
|       } | ||||
|  | ||||
|       return 0 | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     total: { | ||||
|       handler: 'updateTax' | ||||
|     }, | ||||
|     totalTax: { | ||||
|       handler: 'updateTax' | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     updateTax () { | ||||
|       this.$emit('update', { | ||||
|         'index': this.index, | ||||
|         'item': { | ||||
|           ...this.tax, | ||||
|           amount: this.taxAmount | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|  | ||||
| </style> | ||||
							
								
								
									
										560
									
								
								resources/assets/js/views/estimates/Index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										560
									
								
								resources/assets/js/views/estimates/Index.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,560 @@ | ||||
| <template> | ||||
|   <div class="estimate-index-page estimates main-content"> | ||||
|     <div class="page-header"> | ||||
|       <h3 class="page-title">{{ $t('estimates.title') }}</h3> | ||||
|       <ol class="breadcrumb"> | ||||
|         <li class="breadcrumb-item"> | ||||
|           <router-link | ||||
|             slot="item-title" | ||||
|             to="dashboard"> | ||||
|             {{ $t('general.home') }} | ||||
|           </router-link> | ||||
|         </li> | ||||
|         <li class="breadcrumb-item"> | ||||
|           <router-link | ||||
|             slot="item-title" | ||||
|             to="#"> | ||||
|             {{ $tc('estimates.estimate', 2) }} | ||||
|           </router-link> | ||||
|         </li> | ||||
|       </ol> | ||||
|       <div class="page-actions row"> | ||||
|         <div class="col-xs-2 mr-4"> | ||||
|           <base-button | ||||
|             v-show="totalEstimates || filtersApplied" | ||||
|             :outline="true" | ||||
|             :icon="filterIcon" | ||||
|             size="large" | ||||
|             color="theme" | ||||
|             right-icon | ||||
|             @click="toggleFilter" | ||||
|           > | ||||
|             {{ $t('general.filter') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|         <router-link slot="item-title" class="col-xs-2" to="estimates/create"> | ||||
|           <base-button | ||||
|             size="large" | ||||
|             icon="plus" | ||||
|             color="theme" > | ||||
|             {{ $t('estimates.new_estimate') }}</base-button> | ||||
|         </router-link> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <transition name="fade"> | ||||
|       <div v-show="showFilters" class="filter-section"> | ||||
|         <div class="filter-container"> | ||||
|           <div class="filter-customer"> | ||||
|             <label>{{ $tc('customers.customer',1) }} </label> | ||||
|             <base-customer-select | ||||
|               ref="customerSelect" | ||||
|               @select="onSelectCustomer" | ||||
|               @deselect="clearCustomerSearch" | ||||
|             /> | ||||
|           </div> | ||||
|           <div class="filter-status"> | ||||
|             <label>{{ $t('estimates.status') }}</label> | ||||
|             <base-select | ||||
|               v-model="filters.status" | ||||
|               :options="status" | ||||
|               :searchable="true" | ||||
|               :show-labels="false" | ||||
|               :placeholder="$t('general.select_a_status')" | ||||
|               @remove="clearStatusSearch()" | ||||
|             /> | ||||
|           </div> | ||||
|           <div class="filter-date"> | ||||
|             <div class="from pr-3"> | ||||
|               <label>{{ $t('general.from') }}</label> | ||||
|               <base-date-picker | ||||
|                 v-model="filters.from_date" | ||||
|                 :calendar-button="true" | ||||
|                 calendar-button-icon="calendar" | ||||
|               /> | ||||
|             </div> | ||||
|             <div class="dashed" /> | ||||
|             <div class="to pl-3"> | ||||
|               <label>{{ $t('general.to') }}</label> | ||||
|               <base-date-picker | ||||
|                 v-model="filters.to_date" | ||||
|                 :calendar-button="true" | ||||
|                 calendar-button-icon="calendar" | ||||
|               /> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="filter-estimate"> | ||||
|             <label>{{ $t('estimates.estimate_number') }}</label> | ||||
|             <base-input | ||||
|               v-model="filters.estimate_number" | ||||
|               icon="hashtag"/> | ||||
|           </div> | ||||
|         </div> | ||||
|         <label class="clear-filter" @click="clearFilter">{{ $t('general.clear_all') }}</label> | ||||
|       </div> | ||||
|     </transition> | ||||
|     <div v-cloak v-show="showEmptyScreen" class="col-xs-1 no-data-info" align="center"> | ||||
|       <moon-walker-icon class="mt-5 mb-4"/> | ||||
|       <div class="row" align="center"> | ||||
|         <label class="col title">{{ $t('estimates.no_estimates') }}</label> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <label class="description col mt-1" align="center">{{ $t('estimates.list_of_estimates') }}</label> | ||||
|       </div> | ||||
|       <div class="btn-container"> | ||||
|         <base-button | ||||
|           :outline="true" | ||||
|           color="theme" | ||||
|           class="mt-3" | ||||
|           size="large" | ||||
|           @click="$router.push('estimates/create')" | ||||
|         > | ||||
|           {{ $t('estimates.add_new_estimate') }} | ||||
|         </base-button> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <div v-show="!showEmptyScreen" class="table-container"> | ||||
|       <div class="table-actions mt-5"> | ||||
|         <p class="table-stats">{{ $t('general.showing') }}: <b>{{ estimates.length }}</b> {{ $t('general.of') }} <b>{{ totalEstimates }}</b></p> | ||||
|  | ||||
|         <!-- Tabs --> | ||||
|         <ul class="tabs"> | ||||
|           <li class="tab" @click="getStatus('DRAFT')"> | ||||
|             <a :class="['tab-link', {'a-active': filters.status === 'DRAFT'}]" href="#">{{ $t('general.draft') }}</a> | ||||
|           </li> | ||||
|           <li class="tab" @click="getStatus('SENT')"> | ||||
|             <a :class="['tab-link', {'a-active': filters.status === 'SENT'}]" href="#" >{{ $t('general.sent') }}</a> | ||||
|           </li> | ||||
|           <li class="tab" @click="getStatus('')"> | ||||
|             <a :class="['tab-link', {'a-active': filters.status === '' || filters.status === null}]" href="#">{{ $t('general.all') }}</a> | ||||
|           </li> | ||||
|         </ul> | ||||
|         <transition name="fade"> | ||||
|           <v-dropdown v-if="selectedEstimates.length" :show-arrow="false"> | ||||
|             <span slot="activator" href="#" class="table-actions-button dropdown-toggle"> | ||||
|               {{ $t('general.actions') }} | ||||
|             </span> | ||||
|             <v-dropdown-item> | ||||
|               <div class="dropdown-item" @click="removeMultipleEstimates"> | ||||
|                 <font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" /> | ||||
|                 {{ $t('general.delete') }} | ||||
|               </div> | ||||
|             </v-dropdown-item> | ||||
|           </v-dropdown> | ||||
|         </transition> | ||||
|       </div> | ||||
|  | ||||
|       <div class="custom-control custom-checkbox"> | ||||
|         <input | ||||
|           id="select-all" | ||||
|           v-model="selectAllFieldStatus" | ||||
|           :disabled="estimates.length <= 0" | ||||
|           type="checkbox" | ||||
|           class="custom-control-input" | ||||
|           @change="selectAllEstimates" | ||||
|         > | ||||
|         <label v-show="!isRequestOngoing" for="select-all" class="custom-control-label selectall"> | ||||
|           <span class="select-all-label">{{ $t('general.select_all') }} </span> | ||||
|         </label> | ||||
|       </div> | ||||
|  | ||||
|       <table-component | ||||
|         ref="table" | ||||
|         :show-filter="false" | ||||
|         :data="fetchData" | ||||
|         table-class="table" | ||||
|       > | ||||
|         <table-column | ||||
|           :sortable="false" | ||||
|           :filterable="false" | ||||
|           cell-class="no-click" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <div class="custom-control custom-checkbox"> | ||||
|               <input | ||||
|                 :id="row.id" | ||||
|                 v-model="selectField" | ||||
|                 :value="row.id" | ||||
|                 type="checkbox" | ||||
|                 class="custom-control-input" | ||||
|               > | ||||
|               <label :for="row.id" class="custom-control-label" /> | ||||
|             </div> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <table-column | ||||
|           :label="$t('estimates.date')" | ||||
|           sort-as="estimate_date" | ||||
|           show="formattedEstimateDate" /> | ||||
|         <table-column | ||||
|           :label="$t('estimates.contact')" | ||||
|           sort-as="name" | ||||
|           show="name" /> | ||||
|         <table-column | ||||
|           :label="$t('estimates.expiry_date')" | ||||
|           sort-as="expiry_date" | ||||
|           show="formattedExpiryDate" /> | ||||
|         <table-column | ||||
|           :label="$t('estimates.status')" | ||||
|           show="status" > | ||||
|           <template slot-scope="row" > | ||||
|             <span> {{ $t('estimates.status') }}</span> | ||||
|             <span :class="'est-status-'+row.status.toLowerCase()">{{ row.status }}</span> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <table-column | ||||
|           :label="$tc('estimates.estimate', 1)" | ||||
|           show="estimate_number"/> | ||||
|         <table-column | ||||
|           :label="$t('invoices.total')" | ||||
|           sort-as="total" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <div v-html="$utils.formatMoney(row.total, row.user.currency)" /> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <table-column | ||||
|           :sortable="false" | ||||
|           :filterable="false" | ||||
|           cell-class="action-dropdown" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <span> {{ $t('estimates.action') }} </span> | ||||
|             <v-dropdown> | ||||
|               <a slot="activator" href="#"> | ||||
|                 <dot-icon /> | ||||
|               </a> | ||||
|               <v-dropdown-item> | ||||
|                 <router-link :to="{path: `estimates/${row.id}/edit`}" class="dropdown-item"> | ||||
|                   <font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon" /> | ||||
|                   {{ $t('general.edit') }} | ||||
|                 </router-link> | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item> | ||||
|                 <div class="dropdown-item" @click="removeEstimate(row.id)"> | ||||
|                   <font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" /> | ||||
|                   {{ $t('general.delete') }} | ||||
|                 </div> | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item> | ||||
|                 <router-link :to="{path: `estimates/${row.id}/view`}" class="dropdown-item"> | ||||
|                   <font-awesome-icon icon="eye" class="dropdown-item-icon" /> | ||||
|                   {{ $t('general.view') }} | ||||
|                 </router-link> | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item> | ||||
|                 <a class="dropdown-item" href="#" @click="convertInToinvoice(row.id)"> | ||||
|                   <font-awesome-icon icon="envelope" class="dropdown-item-icon" /> | ||||
|                   {{ $t('estimates.convert_to_invoice') }} | ||||
|                 </a> | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item> | ||||
|                 <a class="dropdown-item" href="#" @click.self="onMarkAsSent(row.id)"> | ||||
|                   <font-awesome-icon icon="check-circle" class="dropdown-item-icon" /> | ||||
|                   {{ $t('estimates.mark_as_sent') }} | ||||
|                 </a> | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item> | ||||
|                 <a class="dropdown-item" href="#" @click.self="sendEstimate(row.id)"> | ||||
|                   <font-awesome-icon icon="paper-plane" class="dropdown-item-icon" /> | ||||
|                   {{ $t('estimates.send_estimate') }} | ||||
|                 </a> | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item v-if="row.status === 'DRAFT'"> | ||||
|                 <a class="dropdown-item" href="#" @click.self="onMarkAsAccepted(row.id)"> | ||||
|                   <font-awesome-icon icon="check-circle" class="dropdown-item-icon" /> | ||||
|                   {{ $t('estimates.mark_as_accepted') }} | ||||
|                 </a> | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item v-if="row.status === 'DRAFT'"> | ||||
|                 <a class="dropdown-item" href="#" @click.self="onMarkAsRejected(row.id)"> | ||||
|                   <font-awesome-icon icon="times-circle" class="dropdown-item-icon" /> | ||||
|                   {{ $t('estimates.mark_as_rejected') }} | ||||
|                 </a> | ||||
|               </v-dropdown-item> | ||||
|             </v-dropdown> | ||||
|           </template> | ||||
|         </table-column> | ||||
|       </table-component> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
| import MoonWalkerIcon from '../../../js/components/icon/MoonwalkerIcon' | ||||
| import { SweetModal, SweetModalTab } from 'sweet-modal-vue' | ||||
| import ObservatoryIcon from '../../components/icon/ObservatoryIcon' | ||||
| import moment from 'moment' | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     'moon-walker-icon': MoonWalkerIcon | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       showFilters: false, | ||||
|       currency: null, | ||||
|       status: ['DRAFT', 'SENT', 'VIEWED', 'EXPIRED', 'ACCEPTED', 'REJECTED'], | ||||
|       filtersApplied: false, | ||||
|       isRequestOngoing: true, | ||||
|       filters: { | ||||
|         customer: '', | ||||
|         status: 'DRAFT', | ||||
|         from_date: '', | ||||
|         to_date: '', | ||||
|         estimate_number: '' | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   computed: { | ||||
|     focus, | ||||
|     showEmptyScreen () { | ||||
|       return !this.totalEstimates && !this.isRequestOngoing && !this.filtersApplied | ||||
|     }, | ||||
|     filterIcon () { | ||||
|       return (this.showFilters) ? 'times' : 'filter' | ||||
|     }, | ||||
|     ...mapGetters('customer', [ | ||||
|       'customers' | ||||
|     ]), | ||||
|     ...mapGetters('estimate', [ | ||||
|       'selectedEstimates', | ||||
|       'totalEstimates', | ||||
|       'estimates', | ||||
|       'selectAllField' | ||||
|     ]), | ||||
|     selectField: { | ||||
|       get: function () { | ||||
|         return this.selectedEstimates | ||||
|       }, | ||||
|       set: function (val) { | ||||
|         this.selectEstimate(val) | ||||
|       } | ||||
|     }, | ||||
|     selectAllFieldStatus: { | ||||
|       get: function () { | ||||
|         return this.selectAllField | ||||
|       }, | ||||
|       set: function (val) { | ||||
|         this.setSelectAllState(val) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     filters: { | ||||
|       handler: 'setFilters', | ||||
|       deep: true | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     this.fetchCustomers() | ||||
|   }, | ||||
|   destroyed () { | ||||
|     if (this.selectAllField) { | ||||
|       this.selectAllEstimates() | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('estimate', [ | ||||
|       'fetchEstimates', | ||||
|       'resetSelectedEstimates', | ||||
|       'getRecord', | ||||
|       'selectEstimate', | ||||
|       'selectAllEstimates', | ||||
|       'deleteEstimate', | ||||
|       'deleteMultipleEstimates', | ||||
|       'markAsSent', | ||||
|       'convertToInvoice', | ||||
|       'setSelectAllState', | ||||
|       'markAsAccepted', | ||||
|       'markAsRejected', | ||||
|       'sendEmail' | ||||
|     ]), | ||||
|     ...mapActions('customer', [ | ||||
|       'fetchCustomers' | ||||
|     ]), | ||||
|     refreshTable () { | ||||
|       this.$refs.table.refresh() | ||||
|     }, | ||||
|     getStatus (val) { | ||||
|       this.filters.status = val | ||||
|     }, | ||||
|     async fetchData ({ page, filter, sort }) { | ||||
|       let data = { | ||||
|         customer_id: this.filters.customer === '' ? this.filters.customer : this.filters.customer.id, | ||||
|         status: this.filters.status, | ||||
|         from_date: this.filters.from_date === '' ? this.filters.from_date : moment(this.filters.from_date).format('DD/MM/YYYY'), | ||||
|         to_date: this.filters.to_date === '' ? this.filters.to_date : moment(this.filters.to_date).format('DD/MM/YYYY'), | ||||
|         estimate_number: this.filters.estimate_number, | ||||
|         orderByField: sort.fieldName || 'created_at', | ||||
|         orderBy: sort.order || 'desc', | ||||
|         page | ||||
|       } | ||||
|  | ||||
|       this.isRequestOngoing = true | ||||
|       let response = await this.fetchEstimates(data) | ||||
|       this.isRequestOngoing = false | ||||
|  | ||||
|       this.currency = response.data.currency | ||||
|  | ||||
|       return { | ||||
|         data: response.data.estimates.data, | ||||
|         pagination: { | ||||
|           totalPages: response.data.estimates.last_page, | ||||
|           currentPage: page, | ||||
|           count: response.data.estimates.count | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     async onMarkAsAccepted (id) { | ||||
|       const data = { | ||||
|         id: id | ||||
|       } | ||||
|       let response = await this.markAsAccepted(data) | ||||
|       this.refreshTable() | ||||
|       if (response.data) { | ||||
|         this.filters.status = 'ACCEPTED' | ||||
|         this.$refs.table.refresh() | ||||
|         window.toastr['success'](this.$tc('estimates.marked_as_rejected_message')) | ||||
|       } | ||||
|     }, | ||||
|     async onMarkAsRejected (id) { | ||||
|       const data = { | ||||
|         id: id | ||||
|       } | ||||
|       let response = await this.markAsRejected(data) | ||||
|       this.refreshTable() | ||||
|       if (response.data) { | ||||
|         this.filters.status = 'REJECTED' | ||||
|         this.$refs.table.refresh() | ||||
|         window.toastr['success'](this.$tc('estimates.marked_as_rejected_message')) | ||||
|       } | ||||
|     }, | ||||
|     setFilters () { | ||||
|       this.filtersApplied = true | ||||
|       this.resetSelectedEstimates() | ||||
|       this.$refs.table.refresh() | ||||
|     }, | ||||
|     clearFilter () { | ||||
|       if (this.filters.customer) { | ||||
|         this.$refs.customerSelect.$refs.baseSelect.removeElement(this.filters.customer) | ||||
|       } | ||||
|  | ||||
|       this.filters = { | ||||
|         customer: '', | ||||
|         status: '', | ||||
|         from_date: '', | ||||
|         to_date: '', | ||||
|         estimate_number: '' | ||||
|       } | ||||
|  | ||||
|       this.$nextTick(() => { | ||||
|         this.filtersApplied = false | ||||
|       }) | ||||
|     }, | ||||
|     toggleFilter () { | ||||
|       if (this.showFilters && this.filtersApplied) { | ||||
|         this.clearFilter() | ||||
|         this.refreshTable() | ||||
|       } | ||||
|  | ||||
|       this.showFilters = !this.showFilters | ||||
|     }, | ||||
|     onSelectCustomer (customer) { | ||||
|       this.filters.customer = customer | ||||
|     }, | ||||
|     async removeEstimate (id) { | ||||
|       this.id = id | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$tc('estimates.confirm_delete', 1), | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let res = await this.deleteEstimate(this.id) | ||||
|           if (res.data.success) { | ||||
|             this.$refs.table.refresh() | ||||
|             this.filtersApplied = false | ||||
|             this.resetSelectedEstimates() | ||||
|             window.toastr['success'](this.$tc('estimates.deleted_message', 1)) | ||||
|           } else if (res.data.error) { | ||||
|             window.toastr['error'](res.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     async convertInToinvoice (id) { | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$t('estimates.confirm_conversion'), | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let res = await this.convertToInvoice(id) | ||||
|           if (res.data) { | ||||
|             window.toastr['success'](this.$t('estimates.conversion_message')) | ||||
|             this.$router.push(`invoices/${res.data.invoice.id}/edit`) | ||||
|           } else if (res.data.error) { | ||||
|             window.toastr['error'](res.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     async removeMultipleEstimates () { | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$tc('estimates.confirm_delete', 2), | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let res = await this.deleteMultipleEstimates() | ||||
|           if (res.data.success) { | ||||
|             this.$refs.table.refresh() | ||||
|             this.resetSelectedEstimates() | ||||
|             this.filtersApplied = false | ||||
|             window.toastr['success'](this.$tc('estimates.deleted_message', 2)) | ||||
|           } else if (res.data.error) { | ||||
|             window.toastr['error'](res.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     async clearCustomerSearch (removedOption, id) { | ||||
|       this.filters.customer = '' | ||||
|       this.refreshTable() | ||||
|     }, | ||||
|     async clearStatusSearch (removedOption, id) { | ||||
|       this.filters.status = '' | ||||
|       this.refreshTable() | ||||
|     }, | ||||
|     async onMarkAsSent (id) { | ||||
|       const data = { | ||||
|         id: id | ||||
|       } | ||||
|       let response = await this.markAsSent(data) | ||||
|       this.refreshTable() | ||||
|       if (response.data) { | ||||
|         window.toastr['success'](this.$tc('estimates.mark_as_sent')) | ||||
|       } | ||||
|     }, | ||||
|     async sendEstimate (id) { | ||||
|       const data = { | ||||
|         id: id | ||||
|       } | ||||
|       let response = await this.sendEmail(data) | ||||
|       this.refreshTable() | ||||
|       if (response.data) { | ||||
|         window.toastr['success'](this.$tc('estimates.mark_as_sent')) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										402
									
								
								resources/assets/js/views/estimates/Item.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										402
									
								
								resources/assets/js/views/estimates/Item.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,402 @@ | ||||
| <template> | ||||
|   <tr class="item-row estimate-item-row"> | ||||
|     <td colspan="5"> | ||||
|       <table class="full-width"> | ||||
|         <colgroup> | ||||
|           <col style="width: 40%;"> | ||||
|           <col style="width: 10%;"> | ||||
|           <col style="width: 15%;"> | ||||
|           <col v-if="discountPerItem === 'YES'" style="width: 15%;"> | ||||
|           <col style="width: 15%;"> | ||||
|         </colgroup> | ||||
|         <tbody> | ||||
|           <tr> | ||||
|             <td class=""> | ||||
|               <div class="item-select-wrapper"> | ||||
|                 <div class="sort-icon-wrapper handle"> | ||||
|                   <font-awesome-icon | ||||
|                     class="sort-icon" | ||||
|                     icon="grip-vertical" | ||||
|                   /> | ||||
|                 </div> | ||||
|                 <item-select | ||||
|                   ref="itemSelect" | ||||
|                   :invalid="$v.item.name.$error" | ||||
|                   :invalid-description="$v.item.description.$error" | ||||
|                   :item="item" | ||||
|                   @search="searchVal" | ||||
|                   @select="onSelectItem" | ||||
|                   @deselect="deselectItem" | ||||
|                   @onDesriptionInput="$v.item.description.$touch()" | ||||
|                 /> | ||||
|               </div> | ||||
|             </td> | ||||
|             <td class="text-right"> | ||||
|               <base-input | ||||
|                 v-model="item.quantity" | ||||
|                 :invalid="$v.item.quantity.$error" | ||||
|                 type="number" | ||||
|                 small | ||||
|                 @keyup="updateItem" | ||||
|                 @input="$v.item.quantity.$touch()" | ||||
|               /> | ||||
|               <div v-if="$v.item.quantity.$error"> | ||||
|                 <span v-if="!$v.item.quantity.maxLength" class="text-danger">{{ $t('validation.quantity_maxlength') }}</span> | ||||
|               </div> | ||||
|             </td> | ||||
|             <td class="text-left"> | ||||
|               <div class="d-flex flex-column"> | ||||
|                 <div class="flex-fillbd-highlight"> | ||||
|                   <div class="base-input"> | ||||
|                     <money | ||||
|                       v-model="price" | ||||
|                       v-bind="customerCurrency" | ||||
|                       class="input-field" | ||||
|                       @input="$v.item.price.$touch()" | ||||
|                     /> | ||||
|                   </div> | ||||
|                   <div v-if="$v.item.price.$error"> | ||||
|                     <span v-if="!$v.item.price.maxLength" class="text-danger">{{ $t('validation.price_maxlength') }}</span> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </td> | ||||
|             <td v-if="discountPerItem === 'YES'" class=""> | ||||
|               <div class="d-flex flex-column bd-highlight"> | ||||
|                 <div | ||||
|                   class="btn-group flex-fill bd-highlight" | ||||
|                   role="group" | ||||
|                 > | ||||
|                   <base-input | ||||
|                     v-model="discount" | ||||
|                     :invalid="$v.item.discount_val.$error" | ||||
|                     input-class="item-discount" | ||||
|                     @input="$v.item.discount_val.$touch()" | ||||
|                   /> | ||||
|                   <v-dropdown :show-arrow="false" theme-light> | ||||
|                     <button | ||||
|                       slot="activator" | ||||
|                       type="button" | ||||
|                       class="btn item-dropdown dropdown-toggle" | ||||
|                       data-toggle="dropdown" | ||||
|                       aria-haspopup="true" | ||||
|                       aria-expanded="false" | ||||
|                     > | ||||
|                       {{ item.discount_type == 'fixed' ? currency.symbol : '%' }} | ||||
|                     </button> | ||||
|                     <v-dropdown-item> | ||||
|                       <a class="dropdown-item" href="#" @click.prevent="selectFixed" > | ||||
|                         {{ $t('general.fixed') }} | ||||
|                       </a> | ||||
|                     </v-dropdown-item> | ||||
|                     <v-dropdown-item> | ||||
|                       <a class="dropdown-item" href="#" @click.prevent="selectPercentage"> | ||||
|                         {{ $t('general.percentage') }} | ||||
|                       </a> | ||||
|                     </v-dropdown-item> | ||||
|                   </v-dropdown> | ||||
|                 </div> | ||||
|                 <!-- <div v-if="$v.item.discount.$error"> discount error </div> --> | ||||
|               </div> | ||||
|             </td> | ||||
|             <td class="text-right"> | ||||
|               <div class="item-amount"> | ||||
|                 <span> | ||||
|                   <div v-html="$utils.formatMoney(total, currency)" /> | ||||
|                 </span> | ||||
|  | ||||
|                 <div class="remove-icon-wrapper"> | ||||
|                   <font-awesome-icon | ||||
|                     v-if="index > 0" | ||||
|                     class="remove-icon" | ||||
|                     icon="trash-alt" | ||||
|                     @click="removeItem" | ||||
|                   /> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </td> | ||||
|           </tr> | ||||
|           <tr v-if="taxPerItem === 'YES'" class="tax-tr"> | ||||
|             <td /> | ||||
|             <td colspan="4"> | ||||
|               <tax | ||||
|                 v-for="(tax, index) in item.taxes" | ||||
|                 :key="tax.id" | ||||
|                 :index="index" | ||||
|                 :tax-data="tax" | ||||
|                 :taxes="item.taxes" | ||||
|                 :discounted-total="total" | ||||
|                 :total-tax="totalSimpleTax" | ||||
|                 :total="total" | ||||
|                 :currency="currency" | ||||
|                 @update="updateTax" | ||||
|                 @remove="removeTax" | ||||
|               /> | ||||
|             </td> | ||||
|           </tr> | ||||
|         </tbody> | ||||
|       </table> | ||||
|     </td> | ||||
|   </tr> | ||||
| </template> | ||||
| <script> | ||||
| import Guid from 'guid' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import { mapGetters } from 'vuex' | ||||
| import TaxStub from '../../stub/tax' | ||||
| import EstimateStub from '../../stub/estimate' | ||||
| import ItemSelect from './ItemSelect' | ||||
| import Tax from './Tax' | ||||
| const { required, minValue, between, maxLength } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     Tax, | ||||
|     ItemSelect | ||||
|   }, | ||||
|   mixins: [validationMixin], | ||||
|   props: { | ||||
|     itemData: { | ||||
|       type: Object, | ||||
|       default: null | ||||
|     }, | ||||
|     index: { | ||||
|       type: Number, | ||||
|       default: null | ||||
|     }, | ||||
|     type: { | ||||
|       type: String, | ||||
|       default: '' | ||||
|     }, | ||||
|     currency: { | ||||
|       type: [Object, String], | ||||
|       required: true | ||||
|     }, | ||||
|     taxPerItem: { | ||||
|       type: String, | ||||
|       default: '' | ||||
|     }, | ||||
|     discountPerItem: { | ||||
|       type: String, | ||||
|       default: '' | ||||
|     } | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       isClosePopup: false, | ||||
|       itemSelect: null, | ||||
|       item: {...this.itemData}, | ||||
|       maxDiscount: 0, | ||||
|       money: { | ||||
|         decimal: '.', | ||||
|         thousands: ',', | ||||
|         prefix: '$ ', | ||||
|         precision: 2, | ||||
|         masked: false | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('item', [ | ||||
|       'items' | ||||
|     ]), | ||||
|     ...mapGetters('currency', [ | ||||
|       'defaultCurrencyForInput' | ||||
|     ]), | ||||
|     customerCurrency () { | ||||
|       if (this.currency) { | ||||
|         return { | ||||
|           decimal: this.currency.decimal_separator, | ||||
|           thousands: this.currency.thousand_separator, | ||||
|           prefix: this.currency.symbol + ' ', | ||||
|           precision: this.currency.precision, | ||||
|           masked: false | ||||
|         } | ||||
|       } else { | ||||
|         return this.defaultCurrencyForInput | ||||
|       } | ||||
|     }, | ||||
|     subtotal () { | ||||
|       return this.item.price * this.item.quantity | ||||
|     }, | ||||
|     discount: { | ||||
|       get: function () { | ||||
|         return this.item.discount | ||||
|       }, | ||||
|       set: function (newValue) { | ||||
|         if (this.item.discount_type === 'percentage') { | ||||
|           this.item.discount_val = (this.subtotal * newValue) / 100 | ||||
|         } else { | ||||
|           this.item.discount_val = newValue * 100 | ||||
|         } | ||||
|  | ||||
|         this.item.discount = newValue | ||||
|       } | ||||
|     }, | ||||
|     total () { | ||||
|       return this.subtotal - this.item.discount_val | ||||
|     }, | ||||
|     totalSimpleTax () { | ||||
|       return window._.sumBy(this.item.taxes, function (tax) { | ||||
|         if (!tax.compound_tax) { | ||||
|           return tax.amount | ||||
|         } | ||||
|  | ||||
|         return 0 | ||||
|       }) | ||||
|     }, | ||||
|     totalCompoundTax () { | ||||
|       return window._.sumBy(this.item.taxes, function (tax) { | ||||
|         if (tax.compound_tax) { | ||||
|           return tax.amount | ||||
|         } | ||||
|  | ||||
|         return 0 | ||||
|       }) | ||||
|     }, | ||||
|     totalTax () { | ||||
|       return this.totalSimpleTax + this.totalCompoundTax | ||||
|     }, | ||||
|     price: { | ||||
|       get: function () { | ||||
|         if (parseFloat(this.item.price) > 0) { | ||||
|           return this.item.price / 100 | ||||
|         } | ||||
|  | ||||
|         return this.item.price | ||||
|       }, | ||||
|       set: function (newValue) { | ||||
|         if (parseFloat(newValue) > 0) { | ||||
|           this.item.price = newValue * 100 | ||||
|           this.maxDiscount = this.item.price | ||||
|         } else { | ||||
|           this.item.price = newValue | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     item: { | ||||
|       handler: 'updateItem', | ||||
|       deep: true | ||||
|     }, | ||||
|     subtotal (newValue) { | ||||
|       if (this.item.discount_type === 'percentage') { | ||||
|         this.item.discount_val = (this.item.discount * newValue) / 100 | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   validations () { | ||||
|     return { | ||||
|       item: { | ||||
|         name: { | ||||
|           required | ||||
|         }, | ||||
|         quantity: { | ||||
|           required, | ||||
|           minValue: minValue(1), | ||||
|           maxLength: maxLength(10) | ||||
|         }, | ||||
|         price: { | ||||
|           required, | ||||
|           minValue: minValue(1), | ||||
|           maxLength: maxLength(10) | ||||
|         }, | ||||
|         discount_val: { | ||||
|           between: between(0, this.maxDiscount) | ||||
|         }, | ||||
|         description: { | ||||
|           maxLength: maxLength(255) | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     window.hub.$on('checkItems', this.validateItem) | ||||
|     window.hub.$on('newItem', this.onSelectItem) | ||||
|   }, | ||||
|   methods: { | ||||
|     updateTax (data) { | ||||
|       this.$set(this.item.taxes, data.index, data.item) | ||||
|  | ||||
|       let lastTax = this.item.taxes[this.item.taxes.length - 1] | ||||
|  | ||||
|       if (lastTax.tax_type_id !== 0) { | ||||
|         this.item.taxes.push({...TaxStub, id: Guid.raw()}) | ||||
|       } | ||||
|  | ||||
|       this.updateItem() | ||||
|     }, | ||||
|     removeTax (index) { | ||||
|       this.item.taxes.splice(index, 1) | ||||
|  | ||||
|       this.updateItem() | ||||
|     }, | ||||
|     taxWithPercentage ({ name, percent }) { | ||||
|       return `${name} (${percent}%)` | ||||
|     }, | ||||
|     searchVal (val) { | ||||
|       this.item.name = val | ||||
|     }, | ||||
|     deselectItem () { | ||||
|       this.item = {...EstimateStub, id: this.item.id} | ||||
|       this.$nextTick(() => { | ||||
|         this.$refs.itemSelect.$refs.baseSelect.$refs.search.focus() | ||||
|       }) | ||||
|     }, | ||||
|     onSelectItem (item) { | ||||
|       this.item.name = item.name | ||||
|       this.item.price = item.price | ||||
|       this.item.item_id = item.id | ||||
|       this.item.description = item.description | ||||
|  | ||||
|       // if (this.item.taxes.length) { | ||||
|       //   this.item.taxes = {...item.taxes} | ||||
|       // } | ||||
|     }, | ||||
|     selectFixed () { | ||||
|       if (this.item.discount_type === 'fixed') { | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       this.item.discount_val = this.item.discount * 100 | ||||
|       this.item.discount_type = 'fixed' | ||||
|     }, | ||||
|     selectPercentage () { | ||||
|       if (this.item.discount_type === 'percentage') { | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       this.item.discount_val = (this.subtotal * this.item.discount) / 100 | ||||
|  | ||||
|       this.item.discount_type = 'percentage' | ||||
|     }, | ||||
|     updateItem () { | ||||
|       this.$emit('update', { | ||||
|         'index': this.index, | ||||
|         'item': { | ||||
|           ...this.item, | ||||
|           total: this.total, | ||||
|           totalSimpleTax: this.totalSimpleTax, | ||||
|           totalCompoundTax: this.totalCompoundTax, | ||||
|           totalTax: this.totalTax, | ||||
|           tax: this.totalTax, | ||||
|           taxes: [...this.item.taxes] | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     removeItem () { | ||||
|       this.$emit('remove', this.index) | ||||
|     }, | ||||
|     validateItem () { | ||||
|       this.$v.item.$touch() | ||||
|  | ||||
|       if (this.item !== null) { | ||||
|         this.$emit('itemValidate', this.index, !this.$v.$invalid) | ||||
|       } else { | ||||
|         this.$emit('itemValidate', this.index, false) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										140
									
								
								resources/assets/js/views/estimates/ItemSelect.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								resources/assets/js/views/estimates/ItemSelect.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,140 @@ | ||||
| <template> | ||||
|   <div class="item-selector"> | ||||
|     <div v-if="item.item_id" class="selected-item"> | ||||
|       {{ item.name }} | ||||
|  | ||||
|       <span class="deselect-icon" @click="deselectItem"> | ||||
|         <font-awesome-icon icon="times-circle" /> | ||||
|       </span> | ||||
|     </div> | ||||
|     <base-select | ||||
|       v-else | ||||
|       ref="baseSelect" | ||||
|       v-model="itemSelect" | ||||
|       :options="items" | ||||
|       :show-labels="false" | ||||
|       :preserve-search="true" | ||||
|       :initial-search="item.name" | ||||
|       :invalid="invalid" | ||||
|       :placeholder="$t('estimates.item.select_an_item')" | ||||
|       label="name" | ||||
|       class="multi-select-item" | ||||
|       @value="onTextChange" | ||||
|       @select="(val) => $emit('select', val)" | ||||
|     > | ||||
|       <div slot="afterList"> | ||||
|         <button type="button" class="list-add-button" @click="openItemModal"> | ||||
|           <font-awesome-icon class="icon" icon="cart-plus" /> | ||||
|           <label>{{ $t('general.add_new_item') }}</label> | ||||
|         </button> | ||||
|       </div> | ||||
|     </base-select> | ||||
|     <div class="item-description"> | ||||
|       <base-text-area | ||||
|         v-autoresize | ||||
|         v-model.trim="item.description" | ||||
|         :invalid-description="invalidDescription" | ||||
|         :placeholder="$t('estimates.item.type_item_description')" | ||||
|         type="text" | ||||
|         rows="1" | ||||
|         class="description-input" | ||||
|         @input="$emit('onDesriptionInput')" | ||||
|       /> | ||||
|       <div v-if="invalidDescription"> | ||||
|         <span class="text-danger">{{ $tc('validation.description_maxlength') }}</span> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| const { maxLength } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   mixins: [validationMixin], | ||||
|   props: { | ||||
|     item: { | ||||
|       type: Object, | ||||
|       required: true | ||||
|     }, | ||||
|     invalid: { | ||||
|       type: Boolean, | ||||
|       required: false, | ||||
|       default: false | ||||
|     }, | ||||
|     invalidDescription: { | ||||
|       type: Boolean, | ||||
|       required: false, | ||||
|       default: false | ||||
|     } | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       itemSelect: null, | ||||
|       loading: false | ||||
|     } | ||||
|   }, | ||||
|   validations () { | ||||
|     return { | ||||
|       item: { | ||||
|         description: { | ||||
|           maxLength: maxLength(255) | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('item', [ | ||||
|       'items' | ||||
|     ]) | ||||
|   }, | ||||
|   watch: { | ||||
|     invalidDescription (newValue) { | ||||
|       console.log(newValue) | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('modal', [ | ||||
|       'openModal' | ||||
|     ]), | ||||
|     ...mapActions('item', [ | ||||
|       'fetchItems' | ||||
|     ]), | ||||
|     async searchItems (search) { | ||||
|       let data = { | ||||
|         filter: { | ||||
|           name: search, | ||||
|           unit: '', | ||||
|           price: '' | ||||
|         }, | ||||
|         orderByField: '', | ||||
|         orderBy: '', | ||||
|         page: 1 | ||||
|       } | ||||
|  | ||||
|       this.loading = true | ||||
|  | ||||
|       await this.fetchItems(data) | ||||
|  | ||||
|       this.loading = false | ||||
|     }, | ||||
|     onTextChange (val) { | ||||
|       this.searchItems(val) | ||||
|  | ||||
|       this.$emit('search', val) | ||||
|     }, | ||||
|     openItemModal () { | ||||
|       this.openModal({ | ||||
|         'title': 'Add Item', | ||||
|         'componentName': 'ItemModal' | ||||
|       }) | ||||
|     }, | ||||
|     deselectItem () { | ||||
|       this.itemSelect = null | ||||
|       this.$emit('deselect') | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										168
									
								
								resources/assets/js/views/estimates/Tax.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								resources/assets/js/views/estimates/Tax.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,168 @@ | ||||
| <template> | ||||
|   <div class="tax-row"> | ||||
|     <div class="d-flex align-items-center tax-select"> | ||||
|       <label class="bd-highlight pr-2 mb-0" align="right"> | ||||
|         {{ $t('estimates.tax') }} | ||||
|       </label> | ||||
|       <base-select | ||||
|         v-model="selectedTax" | ||||
|         :options="filteredTypes" | ||||
|         :allow-empty="false" | ||||
|         :show-labels="false" | ||||
|         :custom-label="customLabel" | ||||
|         :placeholder="$t('general.select_a_tax')" | ||||
|         track-by="name" | ||||
|         label="name" | ||||
|         @select="(val) => onSelectTax(val)" | ||||
|       > | ||||
|         <div slot="afterList"> | ||||
|           <button type="button" class="list-add-button" @click="openTaxModal"> | ||||
|             <font-awesome-icon class="icon" icon="check-circle" /> | ||||
|             <label>{{ $t('estimates.add_new_tax') }}</label> | ||||
|           </button> | ||||
|         </div> | ||||
|       </base-select> <br> | ||||
|     </div> | ||||
|     <div class="text-right tax-amount"> | ||||
|       <div v-html="$utils.formatMoney(taxAmount, currency)" /> | ||||
|     </div> | ||||
|     <div class="remove-icon-wrapper"> | ||||
|       <font-awesome-icon | ||||
|         v-if="taxes.length && index !== taxes.length - 1" | ||||
|         class="remove-icon" | ||||
|         icon="trash-alt" | ||||
|         @click="removeTax" | ||||
|       /> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
|  | ||||
| export default { | ||||
|   props: { | ||||
|     index: { | ||||
|       type: Number, | ||||
|       required: true | ||||
|     }, | ||||
|     taxData: { | ||||
|       type: Object, | ||||
|       required: true | ||||
|     }, | ||||
|     taxes: { | ||||
|       type: Array, | ||||
|       default: [] | ||||
|     }, | ||||
|     total: { | ||||
|       type: Number, | ||||
|       default: 0 | ||||
|     }, | ||||
|     totalTax: { | ||||
|       type: Number, | ||||
|       default: 0 | ||||
|     }, | ||||
|     currency: { | ||||
|       type: [Object, String], | ||||
|       required: true | ||||
|     } | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       tax: {...this.taxData}, | ||||
|       selectedTax: null | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('taxType', [ | ||||
|       'taxTypes' | ||||
|     ]), | ||||
|     filteredTypes () { | ||||
|       const clonedTypes = this.taxTypes.map(a => ({...a})) | ||||
|  | ||||
|       return clonedTypes.map((taxType) => { | ||||
|         let found = this.taxes.find(tax => tax.tax_type_id === taxType.id) | ||||
|  | ||||
|         if (found) { | ||||
|           taxType.$isDisabled = true | ||||
|         } else { | ||||
|           taxType.$isDisabled = false | ||||
|         } | ||||
|  | ||||
|         return taxType | ||||
|       }) | ||||
|     }, | ||||
|     taxAmount () { | ||||
|       if (this.tax.compound_tax && this.total) { | ||||
|         return ((this.total + this.totalTax) * this.tax.percent) / 100 | ||||
|       } | ||||
|  | ||||
|       if (this.total && this.tax.percent) { | ||||
|         return (this.total * this.tax.percent) / 100 | ||||
|       } | ||||
|  | ||||
|       return 0 | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     total: { | ||||
|       handler: 'updateTax' | ||||
|     }, | ||||
|     totalTax: { | ||||
|       handler: 'updateTax' | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     if (this.taxData.tax_type_id > 0) { | ||||
|       this.selectedTax = this.taxTypes.find(_type => _type.id === this.taxData.tax_type_id) | ||||
|     } | ||||
|  | ||||
|     window.hub.$on('newTax', (val) => { | ||||
|       if (!this.selectedTax) { | ||||
|         this.selectedTax = val | ||||
|         this.onSelectTax(val) | ||||
|       } | ||||
|     }) | ||||
|  | ||||
|     this.updateTax() | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('modal', [ | ||||
|       'openModal' | ||||
|     ]), | ||||
|     customLabel ({ name, percent }) { | ||||
|       return `${name} - ${percent}%` | ||||
|     }, | ||||
|     onSelectTax (val) { | ||||
|       this.tax.percent = val.percent | ||||
|       this.tax.tax_type_id = val.id | ||||
|       this.tax.compound_tax = val.compound_tax | ||||
|       this.tax.name = val.name | ||||
|  | ||||
|       this.updateTax() | ||||
|     }, | ||||
|     updateTax () { | ||||
|       if (this.tax.tax_type_id === 0) { | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       this.$emit('update', { | ||||
|         'index': this.index, | ||||
|         'item': { | ||||
|           ...this.tax, | ||||
|           amount: this.taxAmount | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     removeTax () { | ||||
|       this.$emit('remove', this.index, this.tax) | ||||
|     }, | ||||
|     openTaxModal () { | ||||
|       this.openModal({ | ||||
|         'title': 'Add Tax', | ||||
|         'componentName': 'TaxTypeModal' | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										255
									
								
								resources/assets/js/views/estimates/View.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								resources/assets/js/views/estimates/View.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,255 @@ | ||||
| <template> | ||||
|   <div v-if="estimate" class="main-content estimate-view-page"> | ||||
|     <div class="page-header"> | ||||
|       <h3 class="page-title"> {{ estimate.estimate_number }}</h3> | ||||
|       <div class="page-actions row"> | ||||
|         <div class="col-xs-2"> | ||||
|           <base-button | ||||
|             :loading="isRequestOnGoing" | ||||
|             :disabled="isRequestOnGoing" | ||||
|             :outline="true" | ||||
|             color="theme" | ||||
|             @click="onMarkAsSent" | ||||
|           > | ||||
|             {{ $t('estimates.mark_as_sent') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|         <v-dropdown :close-on-select="false" align="left" class="filter-container"> | ||||
|           <a slot="activator" href="#"> | ||||
|             <base-button color="theme"> | ||||
|               <font-awesome-icon icon="ellipsis-h" /> | ||||
|             </base-button> | ||||
|           </a> | ||||
|           <v-dropdown-item> | ||||
|             <router-link :to="{path: `/admin/estimates/${$route.params.id}/edit`}" class="dropdown-item"> | ||||
|               <font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon"/> | ||||
|               {{ $t('general.edit') }} | ||||
|             </router-link> | ||||
|             <div class="dropdown-item" @click="removeEstimate($route.params.id)"> | ||||
|               <font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" /> | ||||
|               {{ $t('general.delete') }} | ||||
|             </div> | ||||
|           </v-dropdown-item> | ||||
|         </v-dropdown> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="estimate-sidebar"> | ||||
|       <base-loader v-if="isSearching" /> | ||||
|       <div v-else class="side-header"> | ||||
|         <base-input | ||||
|           v-model="searchData.searchText" | ||||
|           :placeholder="$t('general.search')" | ||||
|           input-class="inv-search" | ||||
|           icon="search" | ||||
|           type="text" | ||||
|           align-icon="right" | ||||
|           @input="onSearched()" | ||||
|         /> | ||||
|         <div | ||||
|           class="btn-group ml-3" | ||||
|           role="group" | ||||
|           aria-label="First group" | ||||
|         > | ||||
|           <v-dropdown :close-on-select="false" align="left" class="filter-container"> | ||||
|             <a slot="activator" href="#"> | ||||
|               <base-button class="inv-button inv-filter-fields-btn" color="default" size="medium"> | ||||
|                 <font-awesome-icon icon="filter" /> | ||||
|               </base-button> | ||||
|             </a> | ||||
|  | ||||
|             <div class="filter-items"> | ||||
|               <input | ||||
|                 id="filter_estimate_date" | ||||
|                 v-model="searchData.orderByField" | ||||
|                 type="radio" | ||||
|                 name="filter" | ||||
|                 class="inv-radio" | ||||
|                 value="estimate_date" | ||||
|                 @change="onSearched" | ||||
|               > | ||||
|               <label class="inv-label" for="filter_estimate_date">{{ $t('reports.estimates.estimate_date') }}</label> | ||||
|             </div> | ||||
|             <div class="filter-items"> | ||||
|               <input | ||||
|                 id="filter_due_date" | ||||
|                 v-model="searchData.orderByField" | ||||
|                 type="radio" | ||||
|                 name="filter" | ||||
|                 class="inv-radio" | ||||
|                 value="expiry_date" | ||||
|                 @change="onSearched" | ||||
|               > | ||||
|               <label class="inv-label" for="filter_due_date">{{ $t('estimates.due_date') }}</label> | ||||
|             </div> | ||||
|             <div class="filter-items"> | ||||
|               <input | ||||
|                 id="filter_estimate_number" | ||||
|                 v-model="searchData.orderByField" | ||||
|                 type="radio" | ||||
|                 name="filter" | ||||
|                 class="inv-radio" | ||||
|                 value="estimate_number" | ||||
|                 @change="onSearched" | ||||
|               > | ||||
|               <label class="inv-label" for="filter_estimate_number">{{ $t('estimates.estimate_number') }}</label> | ||||
|             </div> | ||||
|           </v-dropdown> | ||||
|           <base-button class="inv-button inv-filter-sorting-btn" color="default" size="medium" @click="sortData"> | ||||
|             <font-awesome-icon v-if="getOrderBy" icon="sort-amount-up" /> | ||||
|             <font-awesome-icon v-else icon="sort-amount-down" /> | ||||
|           </base-button> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="side-content"> | ||||
|         <router-link | ||||
|           v-for="(estimate,index) in estimates" | ||||
|           :to="`/admin/estimates/${estimate.id}/view`" | ||||
|           :key="index" | ||||
|           class="side-estimate" | ||||
|         > | ||||
|           <div class="left"> | ||||
|             <div class="inv-name">{{ estimate.user.name }}</div> | ||||
|             <div class="inv-number">{{ estimate.estimate_number }}</div> | ||||
|             <div :class="'est-status-'+estimate.status.toLowerCase()" class="inv-status">{{ estimate.status }}</div> | ||||
|           </div> | ||||
|           <div class="right"> | ||||
|             <div class="inv-amount" v-html="$utils.formatMoney(estimate.total, estimate.user.currency)" /> | ||||
|             <div class="inv-date">{{ estimate.formattedEstimateDate }}</div> | ||||
|           </div> | ||||
|         </router-link> | ||||
|         <p v-if="!estimates.length" class="no-result"> | ||||
|           {{ $t('estimates.no_matching_estimates') }} | ||||
|         </p> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="estimate-view-page-container"> | ||||
|       <iframe :src="`${shareableLink}`" class="frame-style"/> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import { mapActions } from 'vuex' | ||||
| const _ = require('lodash') | ||||
| export default { | ||||
|   data () { | ||||
|     return { | ||||
|       id: null, | ||||
|       count: null, | ||||
|       estimates: [], | ||||
|       estimate: null, | ||||
|       currency: null, | ||||
|       shareableLink: null, | ||||
|       searchData: { | ||||
|         orderBy: null, | ||||
|         orderByField: null, | ||||
|         searchText: null | ||||
|       }, | ||||
|       isRequestOnGoing: false, | ||||
|       isSearching: false | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     getOrderBy () { | ||||
|       if (this.searchData.orderBy === 'asc' || this.searchData.orderBy == null) { | ||||
|         return true | ||||
|       } | ||||
|       return false | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     '$route.params.id' (val) { | ||||
|       this.fetchEstimate() | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     this.loadEstimates() | ||||
|     this.onSearched = _.debounce(this.onSearched, 500) | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('estimate', [ | ||||
|       'fetchEstimates', | ||||
|       'fetchViewEstimate', | ||||
|       'getRecord', | ||||
|       'searchEstimate', | ||||
|       'markAsSent', | ||||
|       'deleteEstimate', | ||||
|       'selectEstimate' | ||||
|     ]), | ||||
|     async loadEstimates () { | ||||
|       let response = await this.fetchEstimates() | ||||
|       if (response.data) { | ||||
|         this.estimates = response.data.estimates.data | ||||
|       } | ||||
|       this.fetchEstimate() | ||||
|     }, | ||||
|     async onSearched () { | ||||
|       let data = '' | ||||
|       if (this.searchData.searchText !== '' && this.searchData.searchText !== null && this.searchData.searchText !== undefined) { | ||||
|         data += `search=${this.searchData.searchText}&` | ||||
|       } | ||||
|  | ||||
|       if (this.searchData.orderBy !== null && this.searchData.orderBy !== undefined) { | ||||
|         data += `orderBy=${this.searchData.orderBy}&` | ||||
|       } | ||||
|  | ||||
|       if (this.searchData.orderByField !== null && this.searchData.orderByField !== undefined) { | ||||
|         data += `orderByField=${this.searchData.orderByField}` | ||||
|       } | ||||
|       this.isSearching = true | ||||
|       let response = await this.searchEstimate(data) | ||||
|       this.isSearching = false | ||||
|       if (response.data) { | ||||
|         this.estimates = response.data.estimates.data | ||||
|       } | ||||
|     }, | ||||
|     async fetchEstimate () { | ||||
|       let estimate = await this.fetchViewEstimate(this.$route.params.id) | ||||
|  | ||||
|       if (estimate.data) { | ||||
|         this.estimate = estimate.data.estimate | ||||
|         this.shareableLink = estimate.data.shareable_link | ||||
|         this.currency = estimate.data.estimate.user.currency | ||||
|       } | ||||
|     }, | ||||
|     sortData () { | ||||
|       if (this.searchData.orderBy === 'asc') { | ||||
|         this.searchData.orderBy = 'desc' | ||||
|         this.onSearched() | ||||
|         return true | ||||
|       } | ||||
|       this.searchData.orderBy = 'asc' | ||||
|       this.onSearched() | ||||
|       return true | ||||
|     }, | ||||
|     async onMarkAsSent () { | ||||
|       this.isRequestOnGoing = true | ||||
|       let response = await this.markAsSent({id: this.estimate.id}) | ||||
|       this.isRequestOnGoing = false | ||||
|       if (response.data) { | ||||
|         window.toastr['success'](this.$tc('estimates.mark_as_sent')) | ||||
|       } | ||||
|     }, | ||||
|     async removeEstimate (id) { | ||||
|       this.selectEstimate([parseInt(id)]) | ||||
|       this.id = id | ||||
|       swal({ | ||||
|         title: 'Deleted', | ||||
|         text: 'you will not be able to recover this estimate!', | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let request = await this.deleteEstimate(this.id) | ||||
|           if (request.data.success) { | ||||
|             window.toastr['success'](this.$tc('estimates.deleted_message', 1)) | ||||
|             this.$router.push('/admin/estimates') | ||||
|           } else if (request.data.error) { | ||||
|             window.toastr['error'](request.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										351
									
								
								resources/assets/js/views/expenses/Create.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										351
									
								
								resources/assets/js/views/expenses/Create.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,351 @@ | ||||
| <template> | ||||
|   <div class="main-content expenses"> | ||||
|     <form action="" @submit.prevent="sendData"> | ||||
|       <div class="page-header"> | ||||
|         <h3 class="page-title">{{ isEdit ? $t('expenses.edit_expense') : $t('expenses.new_expense') }}</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/expenses">{{ $tc('expenses.expense', 2) }}</router-link></li> | ||||
|           <li class="breadcrumb-item"><a href="#">{{ isEdit ? $t('expenses.edit_expense') : $t('expenses.new_expense') }}</a></li> | ||||
|         </ol> | ||||
|         <div class="page-actions row header-button-container"> | ||||
|           <div v-if="isReceiptAvailable" class="col-xs-2 mr-4"> | ||||
|             <a :href="getReceiptUrl"> | ||||
|               <base-button | ||||
|                 :loading="isLoading" | ||||
|                 icon="download" | ||||
|                 color="theme" | ||||
|                 outline | ||||
|               > | ||||
|                 {{ $t('expenses.download_receipt') }} | ||||
|               </base-button> | ||||
|             </a> | ||||
|           </div> | ||||
|           <div class="col-xs-2"> | ||||
|             <base-button | ||||
|               :loading="isLoading" | ||||
|               icon="save" | ||||
|               color="theme" | ||||
|               type="submit" | ||||
|             > | ||||
|               {{ isEdit ? $t('expenses.update_expense') : $t('expenses.save_expense') }} | ||||
|             </base-button> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <div class="col-sm-12"> | ||||
|           <div class="card"> | ||||
|             <div class="card-body"> | ||||
|               <div class="row"> | ||||
|                 <!-- <div class="form-group col-sm-6"> | ||||
|                   <label class="control-label">{{ $t('expenses.expense_title') }}</label> | ||||
|                   <input v-model="formData.title" type="text" name="name" class="form-control"> | ||||
|                 </div> --> | ||||
|                 <div class="form-group col-sm-6"> | ||||
|                   <label class="control-label">{{ $t('expenses.category') }}</label><span class="text-danger"> * </span> | ||||
|                   <base-select | ||||
|                     ref="baseSelect" | ||||
|                     v-model="category" | ||||
|                     :options="categories" | ||||
|                     :invalid="$v.category.$error" | ||||
|                     :searchable="true" | ||||
|                     :show-labels="false" | ||||
|                     :placeholder="$t('expenses.categories.select_a_category')" | ||||
|                     label="name" | ||||
|                     track-by="id" | ||||
|                     @input="$v.category.$touch()" | ||||
|                   > | ||||
|                     <div slot="afterList"> | ||||
|                       <button type="button" class="list-add-button" @click="openCategoryModal"> | ||||
|                         <font-awesome-icon class="icon" icon="cart-plus" /> | ||||
|                         <label>{{ $t('settings.expense_category.add_new_category') }}</label> | ||||
|                       </button> | ||||
|                     </div> | ||||
|                   </base-select> | ||||
|                   <div v-if="$v.category.$error"> | ||||
|                     <span v-if="!$v.category.required" class="text-danger">{{ $t('validation.required') }}</span> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <!-- <div class="form-group col-sm-6"> | ||||
|                   <label>{{ $t('expenses.contact') }}</label> | ||||
|                   <select v-model="formData.contact" name="contact" class="form-control ls-select2"> | ||||
|                     <option v-for="(contact, index) in contacts" :key="index" :value="contact.id"> {{ contact.name }}</option> | ||||
|                   </select> | ||||
|                 </div> --> | ||||
|                 <div class="form-group col-sm-6"> | ||||
|                   <label>{{ $t('expenses.expense_date') }}</label><span class="text-danger"> * </span> | ||||
|                   <base-date-picker | ||||
|                     v-model="formData.expense_date" | ||||
|                     :invalid="$v.formData.expense_date.$error" | ||||
|                     :calendar-button="true" | ||||
|                     calendar-button-icon="calendar" | ||||
|                     @change="$v.formData.expense_date.$touch()" | ||||
|                   /> | ||||
|                   <div v-if="$v.formData.expense_date.$error"> | ||||
|                     <span v-if="!$v.formData.expense_date.required" class="text-danger">{{ $t('validation.required') }}</span> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <div class="form-group col-sm-6"> | ||||
|                   <label>{{ $t('expenses.amount') }}</label> <span class="text-danger"> * </span> | ||||
|                   <div class="base-input"> | ||||
|                     <money | ||||
|                       v-model="amount" | ||||
|                       v-bind="defaultCurrencyForInput" | ||||
|                       class="input-field" | ||||
|                       @input="$v.formData.amount.$touch()" | ||||
|                     /> | ||||
|                   </div> | ||||
|                   <div v-if="$v.formData.amount.$error"> | ||||
|                     <span v-if="!$v.formData.amount.required" class="text-danger">{{ $t('validation.required') }}</span> | ||||
|                     <span v-if="!$v.formData.amount.maxLength" class="text-danger">{{ $t('validation.amount_maxlength') }}</span> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <div class="form-group col-sm-6"> | ||||
|                   <label for="description">{{ $t('expenses.note') }}</label> | ||||
|                   <base-text-area | ||||
|                     v-model="formData.notes" | ||||
|                     @input="$v.formData.notes.$touch()" | ||||
|                   /> | ||||
|                   <div v-if="$v.formData.notes.$error"> | ||||
|                     <span v-if="!$v.formData.notes.maxLength" class="text-danger">{{ $t('validation.notes_maxlength') }}</span> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <div class="form-group col-md-6"> | ||||
|                   <label for="description">{{ $t('expenses.receipt') }} : </label> | ||||
|                   <div class="image-upload-box" @click="$refs.file.click()"> | ||||
|                     <input ref="file" class="d-none" type="file" @change="onFileChange"> | ||||
|                     <img v-if="previewReceipt" :src="previewReceipt" class="preview-logo"> | ||||
|                     <div v-else class="upload-content"> | ||||
|                       <font-awesome-icon class="upload-icon" icon="cloud-upload-alt"/> | ||||
|                       <p class="upload-text"> {{ $t('general.choose_file') }} </p> | ||||
|                     </div> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <div class="col-sm-12"> | ||||
|                   <div class="form-group collapse-button-container"> | ||||
|                     <base-button | ||||
|                       :loading="isLoading" | ||||
|                       icon="save" | ||||
|                       color="theme" | ||||
|                       type="submit" | ||||
|                       class="collapse-button" | ||||
|                     > | ||||
|                       {{ isEdit ? $t('expenses.update_expense') : $t('expenses.save_expense') }} | ||||
|                     </base-button> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </form> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import MultiSelect from 'vue-multiselect' | ||||
| import moment from 'moment' | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| const { required, minValue, maxLength } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     MultiSelect | ||||
|   }, | ||||
|   mixins: [validationMixin], | ||||
|   props: { | ||||
|     addname: { | ||||
|       type: String, | ||||
|       default: '' | ||||
|     } | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       formData: { | ||||
|         expense_category_id: null, | ||||
|         expense_date: new Date(), | ||||
|         amount: null, | ||||
|         notes: '' | ||||
|       }, | ||||
|       money: { | ||||
|         decimal: '.', | ||||
|         thousands: ',', | ||||
|         prefix: '$ ', | ||||
|         precision: 2, | ||||
|         masked: false | ||||
|       }, | ||||
|       isReceiptAvailable: false, | ||||
|       isLoading: false, | ||||
|       file: null, | ||||
|       category: null, | ||||
|       passData: [], | ||||
|       contacts: [], | ||||
|       previewReceipt: null, | ||||
|       fileSendUrl: '/api/expenses' | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     category: { | ||||
|       required | ||||
|     }, | ||||
|     formData: { | ||||
|       expense_date: { | ||||
|         required | ||||
|       }, | ||||
|       amount: { | ||||
|         required, | ||||
|         maxLength: maxLength(10), | ||||
|         minValue: minValue(0.1) | ||||
|       }, | ||||
|       notes: { | ||||
|         maxLength: maxLength(255) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('currency', [ | ||||
|       'defaultCurrencyForInput' | ||||
|     ]), | ||||
|     amount: { | ||||
|       get: function () { | ||||
|         return this.formData.amount / 100 | ||||
|       }, | ||||
|       set: function (newValue) { | ||||
|         this.formData.amount = newValue * 100 | ||||
|       } | ||||
|     }, | ||||
|     isEdit () { | ||||
|       if (this.$route.name === 'expenses.edit') { | ||||
|         return true | ||||
|       } | ||||
|       return false | ||||
|     }, | ||||
|     ...mapGetters('category', [ | ||||
|       'categories' | ||||
|     ]), | ||||
|     ...mapGetters('company', [ | ||||
|       'getSelectedCompany' | ||||
|     ]), | ||||
|     getReceiptUrl () { | ||||
|       if (this.isEdit) { | ||||
|         return `/expenses/${this.$route.params.id}/receipt/${this.getSelectedCompany.unique_hash}` | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     category (newValue) { | ||||
|       this.formData.expense_category_id = newValue.id | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     // this.$refs.baseSelect.$refs.search.focus() | ||||
|     this.fetchInitialData() | ||||
|     if (this.isEdit) { | ||||
|       this.getReceipt() | ||||
|     } | ||||
|     window.hub.$on('newCategory', (val) => { | ||||
|       this.category = val | ||||
|     }) | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('expense', [ | ||||
|       'fetchCreateExpense', | ||||
|       'getFile', | ||||
|       'sendFileWithData', | ||||
|       'addExpense', | ||||
|       'updateExpense', | ||||
|       'fetchExpense' | ||||
|     ]), | ||||
|     ...mapActions('modal', [ | ||||
|       'openModal' | ||||
|     ]), | ||||
|     ...mapActions('category', [ | ||||
|       'fetchCategories' | ||||
|     ]), | ||||
|     openCategoryModal () { | ||||
|       this.openModal({ | ||||
|         'title': 'Add Category', | ||||
|         'componentName': 'CategoryModal' | ||||
|       }) | ||||
|       // this.$refs.table.refresh() | ||||
|     }, | ||||
|     onFileChange (e) { | ||||
|       var input = event.target | ||||
|       this.file = input.files[0] | ||||
|       if (input.files && input.files[0]) { | ||||
|         var reader = new FileReader() | ||||
|         reader.onload = (e) => { | ||||
|           this.previewReceipt = e.target.result | ||||
|         } | ||||
|         reader.readAsDataURL(input.files[0]) | ||||
|       } | ||||
|     }, | ||||
|     async getReceipt () { | ||||
|       let res = await axios.get(`/api/expenses/${this.$route.params.id}/show/receipt`) | ||||
|  | ||||
|       if (res.data.error) { | ||||
|         this.isReceiptAvailable = false | ||||
|         return true | ||||
|       } | ||||
|  | ||||
|       this.isReceiptAvailable = true | ||||
|       this.previewReceipt = res.data.image | ||||
|     }, | ||||
|     async fetchInitialData () { | ||||
|       this.fetchCategories() | ||||
|       if (this.isEdit) { | ||||
|         let response = await this.fetchExpense(this.$route.params.id) | ||||
|         this.category = response.data.expense.category | ||||
|         this.formData = { ...response.data.expense } | ||||
|         this.formData.expense_date = moment(this.formData.expense_date).toString() | ||||
|         this.formData.amount = (response.data.expense.amount) | ||||
|         this.fileSendUrl = `/api/expenses/${this.$route.params.id}` | ||||
|       } | ||||
|     }, | ||||
|     async sendData () { | ||||
|       this.$v.category.$touch() | ||||
|       this.$v.formData.$touch() | ||||
|       if (this.$v.$invalid) { | ||||
|         return true | ||||
|       } | ||||
|  | ||||
|       let data = new FormData() | ||||
|  | ||||
|       if (this.file) { | ||||
|         data.append('attachment_receipt', this.file) | ||||
|       } | ||||
|       data.append('expense_category_id', this.formData.expense_category_id) | ||||
|       data.append('expense_date',  moment(this.formData.expense_date).format('DD/MM/YYYY')) | ||||
|       data.append('amount', (this.formData.amount)) | ||||
|       data.append('notes', this.formData.notes) | ||||
|  | ||||
|       if (this.isEdit) { | ||||
|         this.isLoading = true | ||||
|         data.append('_method', 'PUT') | ||||
|         let response = await this.updateExpense({id: this.$route.params.id, editData: data}) | ||||
|         if (response.data.success) { | ||||
|           window.toastr['success'](this.$t('expenses.updated_message')) | ||||
|           this.isLoading = false | ||||
|           this.$router.push('/admin/expenses') | ||||
|           return true | ||||
|         } | ||||
|         window.toastr['error'](response.data.error) | ||||
|       } else { | ||||
|         this.isLoading = true | ||||
|         let response = await this.addExpense(data) | ||||
|         if (response.data.success) { | ||||
|           window.toastr['success'](this.$t('expenses.created_message')) | ||||
|           this.isLoading = false | ||||
|           this.$router.push('/admin/expenses') | ||||
|           this.isLoading = false | ||||
|           return true | ||||
|         } | ||||
|         window.toastr['success'](response.data.success) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										398
									
								
								resources/assets/js/views/expenses/Index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										398
									
								
								resources/assets/js/views/expenses/Index.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,398 @@ | ||||
| <template> | ||||
|   <div class="expenses main-content"> | ||||
|     <div class="page-header"> | ||||
|       <h3 class="page-title">{{ $t('expenses.title') }}</h3> | ||||
|       <ol class="breadcrumb"> | ||||
|         <li class="breadcrumb-item"> | ||||
|           <router-link | ||||
|             slot="item-title" | ||||
|             to="dashboard"> | ||||
|             {{ $t('general.home') }} | ||||
|           </router-link> | ||||
|         </li> | ||||
|         <li class="breadcrumb-item"> | ||||
|           <router-link | ||||
|             slot="item-title" | ||||
|             to="#"> | ||||
|             {{ $tc('expenses.expense',2) }} | ||||
|           </router-link> | ||||
|         </li> | ||||
|       </ol> | ||||
|       <div class="page-actions row"> | ||||
|         <div class="col-xs-2 mr-4"> | ||||
|           <base-button | ||||
|             v-show="totalExpenses || filtersApplied" | ||||
|             :outline="true" | ||||
|             :icon="filterIcon" | ||||
|             size="large" | ||||
|             right-icon | ||||
|             color="theme" | ||||
|             @click="toggleFilter" | ||||
|           > | ||||
|             {{ $t('general.filter') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|         <router-link slot="item-title" class="col-xs-2" to="expenses/create"> | ||||
|           <base-button size="large" icon="plus" color="theme"> | ||||
|             {{ $t('expenses.add_expense') }} | ||||
|           </base-button> | ||||
|         </router-link> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <transition name="fade"> | ||||
|       <div v-show="showFilters" class="filter-section"> | ||||
|         <div class="row"> | ||||
|           <div class="col-md-4"> | ||||
|             <label>{{ $t('expenses.category') }}</label> | ||||
|             <base-select | ||||
|               v-model="filters.category" | ||||
|               :options="categories" | ||||
|               :searchable="true" | ||||
|               :show-labels="false" | ||||
|               :placeholder="$t('expenses.categories.select_a_category')" | ||||
|               label="name" | ||||
|               @click="filter = ! filter" | ||||
|             /> | ||||
|           </div> | ||||
|           <div class="col-md-4"> | ||||
|             <label>{{ $t('expenses.from_date') }}</label> | ||||
|             <base-date-picker | ||||
|               v-model="filters.from_date" | ||||
|               :calendar-button="true" | ||||
|               calendar-button-icon="calendar" | ||||
|             /> | ||||
|           </div> | ||||
|           <div class="col-md-4"> | ||||
|             <label>{{ $t('expenses.to_date') }}</label> | ||||
|             <base-date-picker | ||||
|               v-model="filters.to_date" | ||||
|               :calendar-button="true" | ||||
|               calendar-button-icon="calendar" | ||||
|             /> | ||||
|           </div> | ||||
|         </div> | ||||
|         <label class="clear-filter" @click="clearFilter">{{ $t('general.clear_all') }}</label> | ||||
|       </div> | ||||
|     </transition> | ||||
|  | ||||
|     <div v-cloak v-show="showEmptyScreen" class="col-xs-1 no-data-info" align="center"> | ||||
|       <observatory-icon class="mt-5 mb-4"/> | ||||
|       <div class="row" align="center"> | ||||
|         <label class="col title">{{ $t('expenses.no_expenses') }}</label> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <label class="description col mt-1" align="center">{{ $t('expenses.list_of_expenses') }}</label> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <div class="col"> | ||||
|           <base-button | ||||
|             :outline="true" | ||||
|             color="theme" | ||||
|             class="mt-3" | ||||
|             size="large" | ||||
|             @click="$router.push('expenses/create')" | ||||
|           > | ||||
|             {{ $t('expenses.add_new_expense') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <div v-show="!showEmptyScreen" class="table-container"> | ||||
|  | ||||
|       <div class="table-actions mt-5"> | ||||
|         <p class="table-stats">{{ $t('general.showing') }}: <b>{{ expenses.length }}</b> {{ $t('general.of') }} <b>{{ totalExpenses }}</b></p> | ||||
|         <transition name="fade"> | ||||
|           <v-dropdown v-if="selectedExpenses.length" :show-arrow="false" theme-light class="action mr-5"> | ||||
|             <span slot="activator" href="#" class="table-actions-button dropdown-toggle"> | ||||
|               {{ $t('general.actions') }} | ||||
|             </span> | ||||
|             <v-dropdown-item> | ||||
|               <div class="dropdown-item" @click="removeMultipleExpenses"> | ||||
|                 <font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" /> | ||||
|                 {{ $t('general.delete') }} | ||||
|               </div> | ||||
|             </v-dropdown-item> | ||||
|           </v-dropdown> | ||||
|         </transition> | ||||
|       </div> | ||||
|  | ||||
|       <div class="custom-control custom-checkbox"> | ||||
|         <input | ||||
|           id="select-all" | ||||
|           v-model="selectAllFieldStatus" | ||||
|           type="checkbox" | ||||
|           class="custom-control-input" | ||||
|           @change="selectAllExpenses" | ||||
|         > | ||||
|         <label v-show="!isRequestOngoing" for="select-all" class="custom-control-label selectall"> | ||||
|           <span class="select-all-label">{{ $t('general.select_all') }} </span> | ||||
|         </label> | ||||
|       </div> | ||||
|  | ||||
|       <table-component | ||||
|         ref="table" | ||||
|         :show-filter="false" | ||||
|         :data="fetchData" | ||||
|         table-class="table" | ||||
|       > | ||||
|  | ||||
|         <table-column | ||||
|           :sortable="false" | ||||
|           :filterable="false" | ||||
|           cell-class="no-click" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <div class="custom-control custom-checkbox"> | ||||
|               <input | ||||
|                 :id="row.id" | ||||
|                 v-model="selectField" | ||||
|                 :value="row.id" | ||||
|                 type="checkbox" | ||||
|                 class="custom-control-input" | ||||
|               > | ||||
|               <label :for="row.id" class="custom-control-label" /> | ||||
|             </div> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <table-column | ||||
|           :label="$tc('expenses.categories.category', 1)" | ||||
|           sort-as="name" | ||||
|           show="category.name" | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('expenses.date')" | ||||
|           sort-as="expense_date" | ||||
|           show="formattedExpenseDate" | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('expenses.note')" | ||||
|           sort-as="expense_date" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <div class="notes"> | ||||
|               <div class="note">{{ row.notes }}</div> | ||||
|             </div> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <table-column | ||||
|           :label="$t('expenses.amount')" | ||||
|           sort-as="amount" | ||||
|           show="category.amount" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <span>{{ $t('expenses.amount') }}</span> | ||||
|             <div v-html="$utils.formatMoney(row.amount, defaultCurrency)" /> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <table-column | ||||
|           :sortable="false" | ||||
|           :filterable="false" | ||||
|           cell-class="action-dropdown no-click" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <span>{{ $t('expenses.action') }}</span> | ||||
|             <v-dropdown> | ||||
|               <a slot="activator" href="#"> | ||||
|                 <dot-icon /> | ||||
|               </a> | ||||
|               <v-dropdown-item> | ||||
|                 <router-link :to="{path: `expenses/${row.id}/edit`}" class="dropdown-item"> | ||||
|                   <font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon" /> | ||||
|                   {{ $t('general.edit') }} | ||||
|                 </router-link> | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item> | ||||
|                 <div class="dropdown-item" @click="removeExpense(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 { SweetModal, SweetModalTab } from 'sweet-modal-vue' | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
| import ObservatoryIcon from '../../components/icon/ObservatoryIcon' | ||||
| import MultiSelect from 'vue-multiselect' | ||||
| import moment, { invalid } from 'moment' | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     MultiSelect, | ||||
|     'observatory-icon': ObservatoryIcon, | ||||
|     'SweetModal': SweetModal, | ||||
|     'SweetModalTab': SweetModalTab | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       showFilters: false, | ||||
|       filtersApplied: false, | ||||
|       isRequestOngoing: true, | ||||
|       filters: { | ||||
|         category: null, | ||||
|         from_date: '', | ||||
|         to_date: '' | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     showEmptyScreen () { | ||||
|       return !this.totalExpenses && !this.isRequestOngoing && !this.filtersApplied | ||||
|     }, | ||||
|     filterIcon () { | ||||
|       return (this.showFilters) ? 'times' : 'filter' | ||||
|     }, | ||||
|     ...mapGetters('category', [ | ||||
|       'categories' | ||||
|     ]), | ||||
|     ...mapGetters('expense', [ | ||||
|       'selectedExpenses', | ||||
|       'totalExpenses', | ||||
|       'expenses', | ||||
|       'selectAllField' | ||||
|     ]), | ||||
|     ...mapGetters('currency', [ | ||||
|       'defaultCurrency' | ||||
|     ]), | ||||
|     selectField: { | ||||
|       get: function () { | ||||
|         return this.selectedExpenses | ||||
|       }, | ||||
|       set: function (val) { | ||||
|         this.selectExpense(val) | ||||
|       } | ||||
|     }, | ||||
|     selectAllFieldStatus: { | ||||
|       get: function () { | ||||
|         return this.selectAllField | ||||
|       }, | ||||
|       set: function (val) { | ||||
|         this.setSelectAllState(val) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     filters: { | ||||
|       handler: 'setFilters', | ||||
|       deep: true | ||||
|     } | ||||
|   }, | ||||
|   destroyed () { | ||||
|     if (this.selectAllField) { | ||||
|       this.selectAllExpenses() | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     this.fetchCategories() | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('expense', [ | ||||
|       'fetchExpenses', | ||||
|       'selectExpense', | ||||
|       'deleteExpense', | ||||
|       'deleteMultipleExpenses', | ||||
|       'selectAllExpenses', | ||||
|       'setSelectAllState' | ||||
|     ]), | ||||
|     ...mapActions('category', [ | ||||
|       'fetchCategories' | ||||
|     ]), | ||||
|     async fetchData ({ page, filter, sort }) { | ||||
|       let data = { | ||||
|         expense_category_id: this.filters.category !== null ? this.filters.category.id : '', | ||||
|         from_date: this.filters.from_date === '' ? this.filters.from_date : moment(this.filters.from_date).format('DD/MM/YYYY'), | ||||
|         to_date: this.filters.to_date === '' ? this.filters.to_date : moment(this.filters.to_date).format('DD/MM/YYYY'), | ||||
|         orderByField: sort.fieldName || 'created_at', | ||||
|         orderBy: sort.order || 'desc', | ||||
|         page | ||||
|       } | ||||
|  | ||||
|       this.isRequestOngoing = true | ||||
|       let response = await this.fetchExpenses(data) | ||||
|       this.isRequestOngoing = false | ||||
|  | ||||
|       return { | ||||
|         data: response.data.expenses.data, | ||||
|         pagination: { | ||||
|           totalPages: response.data.expenses.last_page, | ||||
|           currentPage: page, | ||||
|           count: response.data.expenses.count | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     refreshTable () { | ||||
|       this.$refs.table.refresh() | ||||
|     }, | ||||
|     setFilters () { | ||||
|       this.filtersApplied = true | ||||
|       this.refreshTable() | ||||
|     }, | ||||
|     clearFilter () { | ||||
|       this.filters = { | ||||
|         category: null, | ||||
|         from_date: '', | ||||
|         to_date: '' | ||||
|       } | ||||
|  | ||||
|       this.$nextTick(() => { | ||||
|         this.filtersApplied = false | ||||
|       }) | ||||
|     }, | ||||
|     toggleFilter () { | ||||
|       if (this.showFilters && this.filtersApplied) { | ||||
|         this.clearFilter() | ||||
|         this.refreshTable() | ||||
|       } | ||||
|  | ||||
|       this.showFilters = !this.showFilters | ||||
|     }, | ||||
|     async removeExpense (id) { | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$tc('expenses.confirm_delete'), | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let res = await this.deleteExpense(id) | ||||
|           if (res.data.success) { | ||||
|             window.toastr['success'](this.$tc('expenses.deleted_message', 1)) | ||||
|             this.$refs.table.refresh() | ||||
|             return true | ||||
|           } else if (res.data.error) { | ||||
|             window.toastr['error'](res.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     async removeMultipleExpenses () { | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$tc('expenses.confirm_delete', 2), | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let request = await this.deleteMultipleExpenses() | ||||
|           if (request.data.success) { | ||||
|             window.toastr['success'](this.$tc('expenses.deleted_message', 2)) | ||||
|             this.$refs.table.refresh() | ||||
|           } else if (request.data.error) { | ||||
|             window.toastr['error'](request.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										711
									
								
								resources/assets/js/views/invoices/Create.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										711
									
								
								resources/assets/js/views/invoices/Create.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,711 @@ | ||||
| <template> | ||||
|   <div class="invoice-create-page main-content"> | ||||
|     <form v-if="!initLoading" action="" @submit.prevent="submitInvoiceData"> | ||||
|       <div class="page-header"> | ||||
|         <h3 v-if="$route.name === 'invoices.edit'" class="page-title">{{ $t('invoices.edit_invoice') }}</h3> | ||||
|         <h3 v-else class="page-title">{{ $t('invoices.new_invoice') }} </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/invoices">{{ $tc('invoices.invoice', 2) }}</router-link></li> | ||||
|           <li v-if="$route.name === 'invoices.edit'" class="breadcrumb-item">{{ $t('invoices.edit_invoice') }}</li> | ||||
|           <li v-else class="breadcrumb-item">{{ $t('invoices.new_invoice') }}</li> | ||||
|         </ol> | ||||
|         <div class="page-actions row"> | ||||
|           <a v-if="$route.name === 'invoices.edit'" :href="`/invoices/pdf/${newInvoice.unique_hash}`" target="_blank" class="mr-3 base-button btn btn-outline-primary default-size" outline color="theme"> | ||||
|             {{ $t('general.view_pdf') }} | ||||
|           </a> | ||||
|           <base-button | ||||
|             :loading="isLoading" | ||||
|             :disabled="isLoading" | ||||
|             icon="save" | ||||
|             color="theme" | ||||
|             type="submit"> | ||||
|             {{ $t('invoices.save_invoice') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row invoice-input-group"> | ||||
|         <div class="col-md-5"> | ||||
|           <div | ||||
|             v-if="selectedCustomer" class="show-customer"> | ||||
|             <div class="row px-2 mt-1"> | ||||
|               <div class="col col-6"> | ||||
|                 <div v-if="selectedCustomer.billing_address" class="row address-menu"> | ||||
|                   <label class="col-sm-4 px-2 title">{{ $t('general.bill_to') }}</label> | ||||
|                   <div class="col-sm p-0 px-2 content"> | ||||
|                     <label v-if="selectedCustomer.billing_address.name"> | ||||
|                       {{ selectedCustomer.billing_address.name }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.billing_address.address_street_1"> | ||||
|                       {{ selectedCustomer.billing_address.address_street_1 }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.billing_address.address_street_2"> | ||||
|                       {{ selectedCustomer.billing_address.address_street_2 }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.billing_address.city && selectedCustomer.billing_address.state"> | ||||
|                       {{ selectedCustomer.billing_address.city.name }}, {{ selectedCustomer.billing_address.state.name }} {{ selectedCustomer.billing_address.zip }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.billing_address.country"> | ||||
|                       {{ selectedCustomer.billing_address.country.name }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.billing_address.phone"> | ||||
|                       {{ selectedCustomer.billing_address.phone }} | ||||
|                     </label> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="col col-6"> | ||||
|                 <div v-if="selectedCustomer.shipping_address" class="row address-menu"> | ||||
|                   <label class="col-sm-4 px-2 title">{{ $t('general.ship_to') }}</label> | ||||
|                   <div class="col-sm p-0 px-2 content"> | ||||
|                     <label v-if="selectedCustomer.shipping_address.name"> | ||||
|                       {{ selectedCustomer.shipping_address.name }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.shipping_address.address_street_1"> | ||||
|                       {{ selectedCustomer.shipping_address.address_street_1 }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.shipping_address.address_street_2"> | ||||
|                       {{ selectedCustomer.shipping_address.address_street_2 }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.shipping_address.city && selectedCustomer.shipping_address"> | ||||
|                       {{ selectedCustomer.shipping_address.city.name }}, {{ selectedCustomer.shipping_address.state.name }} {{ selectedCustomer.shipping_address.zip }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.shipping_address.country" class="country"> | ||||
|                       {{ selectedCustomer.shipping_address.country.name }} | ||||
|                     </label> | ||||
|                     <label v-if="selectedCustomer.shipping_address.phone" class="phone"> | ||||
|                       {{ selectedCustomer.shipping_address.phone }} | ||||
|                     </label> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="customer-content mb-1"> | ||||
|               <label class="email">{{ selectedCustomer.name }}</label> | ||||
|               <label class="action" @click="removeCustomer">{{ $t('general.remove') }}</label> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
|           <base-popup v-else :class="['add-customer', {'customer-required': $v.selectedCustomer.$error}]" > | ||||
|             <div slot="activator" class="add-customer-action"> | ||||
|               <font-awesome-icon icon="user" class="customer-icon"/> | ||||
|               <div> | ||||
|                 <label>{{ $t('customers.new_customer') }} <span class="text-danger"> * </span></label> | ||||
|                 <p v-if="$v.selectedCustomer.$error && !$v.selectedCustomer.required" class="text-danger"> | ||||
|                   {{ $t('validation.required') }} | ||||
|                 </p> | ||||
|               </div> | ||||
|             </div> | ||||
|             <customer-select-popup type="invoice" /> | ||||
|           </base-popup> | ||||
|         </div> | ||||
|         <div class="col invoice-input"> | ||||
|           <div class="row mb-3"> | ||||
|             <div class="col"> | ||||
|               <label>{{ $tc('invoices.invoice',1) }} {{ $t('invoices.date') }}<span class="text-danger"> * </span></label> | ||||
|               <base-date-picker | ||||
|                 v-model="newInvoice.invoice_date" | ||||
|                 :calendar-button="true" | ||||
|                 calendar-button-icon="calendar" | ||||
|                 @change="$v.newInvoice.invoice_date.$touch()" | ||||
|               /> | ||||
|               <span v-if="$v.newInvoice.invoice_date.$error && !$v.newInvoice.invoice_date.required" class="text-danger"> {{ $t('validation.required') }} </span> | ||||
|             </div> | ||||
|             <div class="col"> | ||||
|               <label>{{ $t('invoices.due_date') }}<span class="text-danger"> * </span></label> | ||||
|               <base-date-picker | ||||
|                 v-model="newInvoice.due_date" | ||||
|                 :invalid="$v.newInvoice.due_date.$error" | ||||
|                 :calendar-button="true" | ||||
|                 calendar-button-icon="calendar" | ||||
|                 @change="$v.newInvoice.due_date.$touch()" | ||||
|               /> | ||||
|               <span v-if="$v.newInvoice.due_date.$error && !$v.newInvoice.due_date.required" class="text-danger mt-1"> {{ $t('validation.required') }}</span> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="row mt-4"> | ||||
|             <div class="col"> | ||||
|               <label>{{ $t('invoices.invoice_number') }}<span class="text-danger"> * </span></label> | ||||
|               <base-input | ||||
|                 :invalid="$v.newInvoice.invoice_number.$error" | ||||
|                 :read-only="true" | ||||
|                 v-model="newInvoice.invoice_number" | ||||
|                 icon="hashtag" | ||||
|                 @input="$v.newInvoice.invoice_number.$touch()" | ||||
|               /> | ||||
|               <span v-show="$v.newInvoice.invoice_number.$error && !$v.newInvoice.invoice_number.required" class="text-danger mt-1"> {{ $tc('validation.required') }}  </span> | ||||
|             </div> | ||||
|             <div class="col"> | ||||
|               <label>{{ $t('invoices.ref_number') }}</label> | ||||
|               <base-input | ||||
|                 v-model="newInvoice.reference_number" | ||||
|                 :invalid="$v.newInvoice.reference_number.$error" | ||||
|                 icon="hashtag" | ||||
|                 type="number" | ||||
|                 @input="$v.newInvoice.reference_number.$touch()" | ||||
|               /> | ||||
|               <div v-if="$v.newInvoice.reference_number.$error" class="text-danger">{{ $tc('validation.ref_number_maxlength') }}</div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <table class="item-table"> | ||||
|         <colgroup> | ||||
|           <col style="width: 40%;"> | ||||
|           <col style="width: 10%;"> | ||||
|           <col style="width: 15%;"> | ||||
|           <col v-if="discountPerItem === 'YES'" style="width: 15%;"> | ||||
|           <col style="width: 15%;"> | ||||
|         </colgroup> | ||||
|         <thead class="item-table-header"> | ||||
|           <tr> | ||||
|             <th class="text-left"> | ||||
|               <span class="column-heading item-heading"> | ||||
|                 {{ $tc('items.item',2) }} | ||||
|               </span> | ||||
|             </th> | ||||
|             <th class="text-right"> | ||||
|               <span class="column-heading"> | ||||
|                 {{ $t('invoices.item.quantity') }} | ||||
|               </span> | ||||
|             </th> | ||||
|             <th class="text-left"> | ||||
|               <span class="column-heading"> | ||||
|                 {{ $t('invoices.item.price') }} | ||||
|               </span> | ||||
|             </th> | ||||
|             <th v-if="discountPerItem === 'YES'" class="text-right"> | ||||
|               <span class="column-heading"> | ||||
|                 {{ $t('invoices.item.discount') }} | ||||
|               </span> | ||||
|             </th> | ||||
|             <th class="text-right"> | ||||
|               <span class="column-heading amount-heading"> | ||||
|                 {{ $t('invoices.item.amount') }} | ||||
|               </span> | ||||
|             </th> | ||||
|           </tr> | ||||
|         </thead> | ||||
|         <draggable v-model="newInvoice.items" class="item-body" tag="tbody" handle=".handle"> | ||||
|           <invoice-item | ||||
|             v-for="(item, index) in newInvoice.items" | ||||
|             :key="item.id" | ||||
|             :index="index" | ||||
|             :item-data="item" | ||||
|             :currency="currency" | ||||
|             :tax-per-item="taxPerItem" | ||||
|             :discount-per-item="discountPerItem" | ||||
|             @remove="removeItem" | ||||
|             @update="updateItem" | ||||
|             @itemValidate="checkItemsData" | ||||
|           /> | ||||
|         </draggable> | ||||
|       </table> | ||||
|       <div class="add-item-action" @click="addItem"> | ||||
|         <font-awesome-icon icon="shopping-basket" class="mr-2"/> | ||||
|         {{ $t('invoices.add_item') }} | ||||
|       </div> | ||||
|  | ||||
|       <div class="invoice-foot"> | ||||
|         <div> | ||||
|           <label>{{ $t('invoices.notes') }}</label> | ||||
|           <base-text-area | ||||
|             v-model="newInvoice.notes" | ||||
|             rows="3" | ||||
|             cols="50" | ||||
|             @input="$v.newInvoice.notes.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.newInvoice.notes.$error"> | ||||
|             <span v-if="!$v.newInvoice.notes.maxLength" class="text-danger">{{ $t('validation.notes_maxlength') }}</span> | ||||
|           </div> | ||||
|           <label class="mt-3 mb-1 d-block">{{ $t('invoices.invoice_template') }} <span class="text-danger"> * </span></label> | ||||
|           <base-button type="button" class="btn-template" icon="pencil-alt" right-icon @click="openTemplateModal" > | ||||
|             <span class="mr-4"> {{ $t('invoices.template') }} {{ getTemplateId }} </span> | ||||
|           </base-button> | ||||
|         </div> | ||||
|  | ||||
|         <div class="invoice-total"> | ||||
|           <div class="section"> | ||||
|             <label class="invoice-label">{{ $t('invoices.sub_total') }}</label> | ||||
|             <label class="invoice-amount"> | ||||
|               <div v-html="$utils.formatMoney(subtotal, currency)" /> | ||||
|             </label> | ||||
|           </div> | ||||
|           <div v-for="tax in allTaxes" :key="tax.tax_type_id" class="section"> | ||||
|             <label class="invoice-label">{{ tax.name }} - {{ tax.percent }}% </label> | ||||
|             <label class="invoice-amount"> | ||||
|               <div v-html="$utils.formatMoney(tax.amount, currency)" /> | ||||
|             </label> | ||||
|           </div> | ||||
|           <div v-if="discountPerItem === 'NO' || discountPerItem === null" class="section mt-2"> | ||||
|             <label class="invoice-label">{{ $t('invoices.discount') }}</label> | ||||
|             <div | ||||
|               class="btn-group discount-drop-down" | ||||
|               role="group" | ||||
|             > | ||||
|               <base-input | ||||
|                 v-model="discount" | ||||
|                 :invalid="$v.newInvoice.discount_val.$error" | ||||
|                 input-class="item-discount" | ||||
|                 @input="$v.newInvoice.discount_val.$touch()" | ||||
|               /> | ||||
|               <v-dropdown :show-arrow="false"> | ||||
|                 <button | ||||
|                   slot="activator" | ||||
|                   type="button" | ||||
|                   class="btn item-dropdown dropdown-toggle" | ||||
|                   data-toggle="dropdown" | ||||
|                   aria-haspopup="true" | ||||
|                   aria-expanded="false" | ||||
|                 > | ||||
|                   {{ newInvoice.discount_type == 'fixed' ? currency.symbol : '%' }} | ||||
|                 </button> | ||||
|                 <v-dropdown-item> | ||||
|                   <a class="dropdown-item" href="#" @click.prevent="selectFixed"> | ||||
|                     {{ $t('general.fixed') }} | ||||
|                   </a> | ||||
|                 </v-dropdown-item> | ||||
|                 <v-dropdown-item> | ||||
|                   <a class="dropdown-item" href="#" @click.prevent="selectPercentage"> | ||||
|                     {{ $t('general.percentage') }} | ||||
|                   </a> | ||||
|                 </v-dropdown-item> | ||||
|               </v-dropdown> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
|           <div v-if="taxPerItem === 'NO' || taxPerItem === null"> | ||||
|             <tax | ||||
|               v-for="(tax, index) in newInvoice.taxes" | ||||
|               :index="index" | ||||
|               :total="subtotalWithDiscount" | ||||
|               :key="tax.id" | ||||
|               :tax="tax" | ||||
|               :taxes="newInvoice.taxes" | ||||
|               :currency="currency" | ||||
|               :total-tax="totalSimpleTax" | ||||
|               @remove="removeInvoiceTax" | ||||
|               @update="updateTax" | ||||
|             /> | ||||
|           </div> | ||||
|  | ||||
|           <base-popup v-if="taxPerItem === 'NO' || taxPerItem === null" ref="taxModal" class="tax-selector"> | ||||
|             <div slot="activator" class="float-right"> | ||||
|               + {{ $t('invoices.add_tax') }} | ||||
|             </div> | ||||
|             <tax-select-popup :taxes="newInvoice.taxes" @select="onSelectTax"/> | ||||
|           </base-popup> | ||||
|  | ||||
|           <div class="section border-top mt-3"> | ||||
|             <label class="invoice-label">{{ $t('invoices.total') }} {{ $t('invoices.amount') }}:</label> | ||||
|             <label class="invoice-amount total"> | ||||
|               <div v-html="$utils.formatMoney(total, currency)" /> | ||||
|             </label> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </form> | ||||
|     <base-loader v-else /> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import draggable from 'vuedraggable' | ||||
| import MultiSelect from 'vue-multiselect' | ||||
| import InvoiceItem from './Item' | ||||
| import InvoiceStub from '../../stub/invoice' | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
| import moment from 'moment' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import Guid from 'guid' | ||||
| import TaxStub from '../../stub/tax' | ||||
| import Tax from './InvoiceTax' | ||||
| const { required, between, maxLength } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     InvoiceItem, | ||||
|     MultiSelect, | ||||
|     Tax, | ||||
|     draggable | ||||
|   }, | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       newInvoice: { | ||||
|         invoice_date: null, | ||||
|         due_date: null, | ||||
|         invoice_number: null, | ||||
|         user_id: null, | ||||
|         invoice_template_id: 1, | ||||
|         sub_total: null, | ||||
|         total: null, | ||||
|         tax: null, | ||||
|         notes: null, | ||||
|         discount_type: 'fixed', | ||||
|         discount_val: 0, | ||||
|         discount: 0, | ||||
|         reference_number: null, | ||||
|         items: [{ | ||||
|           ...InvoiceStub, | ||||
|           id: Guid.raw(), | ||||
|           taxes: [{...TaxStub, id: Guid.raw()}] | ||||
|         }], | ||||
|         taxes: [] | ||||
|       }, | ||||
|       customers: [], | ||||
|       itemList: [], | ||||
|       invoiceTemplates: [], | ||||
|       selectedCurrency: '', | ||||
|       taxPerItem: null, | ||||
|       discountPerItem: null, | ||||
|       initLoading: false, | ||||
|       isLoading: false, | ||||
|       maxDiscount: 0 | ||||
|     } | ||||
|   }, | ||||
|   validations () { | ||||
|     return { | ||||
|       newInvoice: { | ||||
|         invoice_date: { | ||||
|           required | ||||
|         }, | ||||
|         due_date: { | ||||
|           required | ||||
|         }, | ||||
|         invoice_number: { | ||||
|           required | ||||
|         }, | ||||
|         discount_val: { | ||||
|           between: between(0, this.subtotal) | ||||
|         }, | ||||
|         notes: { | ||||
|           maxLength: maxLength(255) | ||||
|         }, | ||||
|         reference_number: { | ||||
|           maxLength: maxLength(10) | ||||
|         } | ||||
|       }, | ||||
|       selectedCustomer: { | ||||
|         required | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('general', [ | ||||
|       'itemDiscount' | ||||
|     ]), | ||||
|     ...mapGetters('currency', [ | ||||
|       'defaultCurrency' | ||||
|     ]), | ||||
|     ...mapGetters('invoice', [ | ||||
|       'getTemplateId', | ||||
|       'selectedCustomer' | ||||
|     ]), | ||||
|     currency () { | ||||
|       return this.selectedCurrency | ||||
|     }, | ||||
|     subtotalWithDiscount () { | ||||
|       return this.subtotal - this.newInvoice.discount_val | ||||
|     }, | ||||
|     total () { | ||||
|       return this.subtotalWithDiscount + this.totalTax | ||||
|     }, | ||||
|     subtotal () { | ||||
|       return this.newInvoice.items.reduce(function (a, b) { | ||||
|         return a + b['total'] | ||||
|       }, 0) | ||||
|     }, | ||||
|     discount: { | ||||
|       get: function () { | ||||
|         return this.newInvoice.discount | ||||
|       }, | ||||
|       set: function (newValue) { | ||||
|         if (this.newInvoice.discount_type === 'percentage') { | ||||
|           this.newInvoice.discount_val = (this.subtotal * newValue) / 100 | ||||
|         } else { | ||||
|           this.newInvoice.discount_val = newValue * 100 | ||||
|         } | ||||
|  | ||||
|         this.newInvoice.discount = newValue | ||||
|       } | ||||
|     }, | ||||
|     totalSimpleTax () { | ||||
|       return window._.sumBy(this.newInvoice.taxes, function (tax) { | ||||
|         if (!tax.compound_tax) { | ||||
|           return tax.amount | ||||
|         } | ||||
|  | ||||
|         return 0 | ||||
|       }) | ||||
|     }, | ||||
|  | ||||
|     totalCompoundTax () { | ||||
|       return window._.sumBy(this.newInvoice.taxes, function (tax) { | ||||
|         if (tax.compound_tax) { | ||||
|           return tax.amount | ||||
|         } | ||||
|  | ||||
|         return 0 | ||||
|       }) | ||||
|     }, | ||||
|     totalTax () { | ||||
|       if (this.taxPerItem === 'NO' || this.taxPerItem === null) { | ||||
|         return this.totalSimpleTax + this.totalCompoundTax | ||||
|       } | ||||
|  | ||||
|       return window._.sumBy(this.newInvoice.items, function (tax) { | ||||
|         return tax.tax | ||||
|       }) | ||||
|     }, | ||||
|     allTaxes () { | ||||
|       let taxes = [] | ||||
|  | ||||
|       this.newInvoice.items.forEach((item) => { | ||||
|         item.taxes.forEach((tax) => { | ||||
|           let found = taxes.find((_tax) => { | ||||
|             return _tax.tax_type_id === tax.tax_type_id | ||||
|           }) | ||||
|  | ||||
|           if (found) { | ||||
|             found.amount += tax.amount | ||||
|           } else if (tax.tax_type_id) { | ||||
|             taxes.push({ | ||||
|               tax_type_id: tax.tax_type_id, | ||||
|               amount: tax.amount, | ||||
|               percent: tax.percent, | ||||
|               name: tax.name | ||||
|             }) | ||||
|           } | ||||
|         }) | ||||
|       }) | ||||
|  | ||||
|       return taxes | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     selectedCustomer (newVal) { | ||||
|       if (newVal && newVal.currency) { | ||||
|         this.selectedCurrency = newVal.currency | ||||
|       } else { | ||||
|         this.selectedCurrency = this.defaultCurrency | ||||
|       } | ||||
|     }, | ||||
|     subtotal (newValue) { | ||||
|       if (this.newInvoice.discount_type === 'percentage') { | ||||
|         this.newInvoice.discount_val = (this.newInvoice.discount * newValue) / 100 | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     this.loadData() | ||||
|     this.fetchInitialItems() | ||||
|     this.resetSelectedCustomer() | ||||
|     window.hub.$on('newTax', this.onSelectTax) | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('modal', [ | ||||
|       'openModal' | ||||
|     ]), | ||||
|     ...mapActions('invoice', [ | ||||
|       'addInvoice', | ||||
|       'fetchCreateInvoice', | ||||
|       'fetchInvoice', | ||||
|       'resetSelectedCustomer', | ||||
|       'selectCustomer', | ||||
|       'updateInvoice' | ||||
|     ]), | ||||
|     ...mapActions('item', [ | ||||
|       'fetchItems' | ||||
|     ]), | ||||
|     isEmpty (obj) { | ||||
|       for (let key in obj) { | ||||
|         if (obj.hasOwnProperty(key)) { | ||||
|           return false | ||||
|         } | ||||
|       } | ||||
|       return true | ||||
|     }, | ||||
|     selectFixed () { | ||||
|       if (this.newInvoice.discount_type === 'fixed') { | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       this.newInvoice.discount_val = this.newInvoice.discount * 100 | ||||
|       this.newInvoice.discount_type = 'fixed' | ||||
|     }, | ||||
|     selectPercentage () { | ||||
|       if (this.newInvoice.discount_type === 'percentage') { | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       this.newInvoice.discount_val = (this.subtotal * this.newInvoice.discount) / 100 | ||||
|  | ||||
|       this.newInvoice.discount_type = 'percentage' | ||||
|     }, | ||||
|     updateTax (data) { | ||||
|       Object.assign(this.newInvoice.taxes[data.index], {...data.item}) | ||||
|     }, | ||||
|     async fetchInitialItems () { | ||||
|       await this.fetchItems({ | ||||
|         filter: {}, | ||||
|         orderByField: '', | ||||
|         orderBy: '' | ||||
|       }) | ||||
|     }, | ||||
|     async loadData () { | ||||
|       if (this.$route.name === 'invoices.edit') { | ||||
|         this.initLoading = true | ||||
|         let response = await this.fetchInvoice(this.$route.params.id) | ||||
|  | ||||
|         if (response.data) { | ||||
|           this.selectCustomer(response.data.invoice.user_id) | ||||
|           this.newInvoice = response.data.invoice | ||||
|           this.discountPerItem = response.data.discount_per_item | ||||
|           this.taxPerItem = response.data.tax_per_item | ||||
|           this.selectedCurrency = this.defaultCurrency | ||||
|           this.invoiceTemplates = response.data.invoiceTemplates | ||||
|         } | ||||
|         this.initLoading = false | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       this.initLoading = true | ||||
|       let response = await this.fetchCreateInvoice() | ||||
|       if (response.data) { | ||||
|         this.discountPerItem = response.data.discount_per_item | ||||
|         this.taxPerItem = response.data.tax_per_item | ||||
|         this.selectedCurrency = this.defaultCurrency | ||||
|         this.invoiceTemplates = response.data.invoiceTemplates | ||||
|         let today = new Date() | ||||
|         this.newInvoice.invoice_date = moment(today).toString() | ||||
|         this.newInvoice.due_date = moment(today).add(7, 'days').toString() | ||||
|         this.newInvoice.invoice_number = response.data.nextInvoiceNumber | ||||
|         this.itemList = response.data.items | ||||
|       } | ||||
|       this.initLoading = false | ||||
|     }, | ||||
|     removeCustomer () { | ||||
|       this.resetSelectedCustomer() | ||||
|     }, | ||||
|     openTemplateModal () { | ||||
|       this.openModal({ | ||||
|         'title': 'Choose a template', | ||||
|         'componentName': 'InvoiceTemplate', | ||||
|         'data': this.invoiceTemplates | ||||
|       }) | ||||
|     }, | ||||
|     addItem () { | ||||
|       this.newInvoice.items.push({...InvoiceStub, id: Guid.raw(), taxes: [{...TaxStub, id: Guid.raw()}]}) | ||||
|     }, | ||||
|     removeItem (index) { | ||||
|       this.newInvoice.items.splice(index, 1) | ||||
|     }, | ||||
|     updateItem (data) { | ||||
|       Object.assign(this.newInvoice.items[data.index], {...data.item}) | ||||
|     }, | ||||
|     submitInvoiceData () { | ||||
|       if (!this.checkValid()) { | ||||
|         return false | ||||
|       } | ||||
|  | ||||
|       this.isLoading = true | ||||
|  | ||||
|       let data = { | ||||
|         ...this.newInvoice, | ||||
|         invoice_date: moment(this.newInvoice.invoice_date).format('DD/MM/YYYY'), | ||||
|         due_date: moment(this.newInvoice.due_date).format('DD/MM/YYYY'), | ||||
|         sub_total: this.subtotal, | ||||
|         total: this.total, | ||||
|         tax: this.totalTax, | ||||
|         user_id: null, | ||||
|         invoice_template_id: this.getTemplateId | ||||
|       } | ||||
|  | ||||
|       if (this.selectedCustomer != null) { | ||||
|         data.user_id = this.selectedCustomer.id | ||||
|       } | ||||
|  | ||||
|       if (this.$route.name === 'invoices.edit') { | ||||
|         this.submitUpdate(data) | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       this.submitSave(data) | ||||
|     }, | ||||
|     submitSave (data) { | ||||
|       this.addInvoice(data).then((res) => { | ||||
|         if (res.data) { | ||||
|           window.toastr['success'](this.$t('invoices.created_message')) | ||||
|           this.$router.push('/admin/invoices') | ||||
|         } | ||||
|  | ||||
|         this.isLoading = false | ||||
|       }).catch((err) => { | ||||
|         this.isLoading = false | ||||
|         console.log(err) | ||||
|       }) | ||||
|     }, | ||||
|     submitUpdate (data) { | ||||
|       this.updateInvoice(data).then((res) => { | ||||
|         this.isLoading = false | ||||
|         if (res.data.success) { | ||||
|           window.toastr['success'](this.$t('invoices.updated_message')) | ||||
|           this.$router.push('/admin/invoices') | ||||
|         } | ||||
|  | ||||
|         if (res.data.error === 'invalid_due_amount') { | ||||
|           window.toastr['error'](this.$t('invoices.invalid_due_amount_message')) | ||||
|         } | ||||
|       }).catch((err) => { | ||||
|         this.isLoading = false | ||||
|         console.log(err) | ||||
|       }) | ||||
|     }, | ||||
|     checkItemsData (index, isValid) { | ||||
|       this.newInvoice.items[index].valid = isValid | ||||
|     }, | ||||
|     onSelectTax (selectedTax) { | ||||
|       let amount = 0 | ||||
|  | ||||
|       if (selectedTax.compound_tax && this.subtotalWithDiscount) { | ||||
|         amount = ((this.subtotalWithDiscount + this.totalSimpleTax) * selectedTax.percent) / 100 | ||||
|       } else if (this.subtotalWithDiscount && selectedTax.percent) { | ||||
|         amount = (this.subtotalWithDiscount * selectedTax.percent) / 100 | ||||
|       } | ||||
|  | ||||
|       this.newInvoice.taxes.push({ | ||||
|         ...TaxStub, | ||||
|         id: Guid.raw(), | ||||
|         name: selectedTax.name, | ||||
|         percent: selectedTax.percent, | ||||
|         compound_tax: selectedTax.compound_tax, | ||||
|         tax_type_id: selectedTax.id, | ||||
|         amount | ||||
|       }) | ||||
|  | ||||
|       this.$refs.taxModal.close() | ||||
|     }, | ||||
|     removeInvoiceTax (index) { | ||||
|       this.newInvoice.taxes.splice(index, 1) | ||||
|     }, | ||||
|     checkValid () { | ||||
|       this.$v.newInvoice.$touch() | ||||
|       this.$v.selectedCustomer.$touch() | ||||
|  | ||||
|       window.hub.$emit('checkItems') | ||||
|       let isValid = true | ||||
|       this.newInvoice.items.forEach((item) => { | ||||
|         if (!item.valid) { | ||||
|           isValid = false | ||||
|         } | ||||
|       }) | ||||
|       if (!this.$v.selectedCustomer.$invalid && this.$v.newInvoice.$invalid === false && isValid === true) { | ||||
|         return true | ||||
|       } | ||||
|       return false | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										589
									
								
								resources/assets/js/views/invoices/Edit.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										589
									
								
								resources/assets/js/views/invoices/Edit.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,589 @@ | ||||
| <template> | ||||
|   <div class="invoice-create-page main-content"> | ||||
|     <form action="" @submit.prevent="submitInvoiceData"> | ||||
|       <div class="page-header"> | ||||
|         <h3 class="page-title">{{ $t('invoices.new_invoice') }}</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/invoices">{{ $tc('invoices.invoice', 2) }}</router-link></li> | ||||
|           <li class="breadcrumb-item">{{ $t('invoices.new_invoice') }}</li> | ||||
|         </ol> | ||||
|         <div class="page-actions row"> | ||||
|           <base-button class="mr-3" outline color="theme"> | ||||
|             {{ $t('general.download_pdf') }} | ||||
|           </base-button> | ||||
|           <base-button | ||||
|             :loading="isLoading" | ||||
|             icon="save" | ||||
|             color="theme" | ||||
|             type="submit"> | ||||
|             {{ $t('invoices.save_invoice') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row invoice-input-group"> | ||||
|         <div class="col-md-5"> | ||||
|           <div | ||||
|             v-if="selectedCustomer" | ||||
|             class="show-customer" | ||||
|           > | ||||
|             <div class="row p-2"> | ||||
|               <div class="col col-6"> | ||||
|                 <div v-if="selectedCustomer.billing_address != null" class="row address-menu"> | ||||
|                   <label class="col-sm-4 px-2 title">{{ $t('general.bill_to') }}</label> | ||||
|                   <div class="col-sm p-0 px-2 content"> | ||||
|                     <label>{{ selectedCustomer.billing_address.name }}</label> | ||||
|                     <label>{{ selectedCustomer.billing_address.address_street_1 }}</label> | ||||
|                     <label>{{ selectedCustomer.billing_address.address_street_2 }}</label> | ||||
|                     <label> | ||||
|                       {{ selectedCustomer.billing_address.city.name }}, {{ selectedCustomer.billing_address.state.name }} {{ selectedCustomer.billing_address.zip }} | ||||
|                     </label> | ||||
|                     <label>{{ selectedCustomer.billing_address.country.name }}</label> | ||||
|                     <label>{{ selectedCustomer.billing_address.phone }}</label> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="col col-6"> | ||||
|                 <div v-if="selectedCustomer.shipping_address != null" class="row address-menu"> | ||||
|                   <label class="col-sm-4 px-2 title">{{ $t('general.ship_to') }}</label> | ||||
|                   <div class="col-sm p-0 px-2 content"> | ||||
|                     <label>{{ selectedCustomer.shipping_address.name }}</label> | ||||
|                     <label>{{ selectedCustomer.shipping_address.address_street_1 }}</label> | ||||
|                     <label>{{ selectedCustomer.shipping_address.address_street_2 }}</label> | ||||
|                     <label v-show="selectedCustomer.shipping_address.city"> | ||||
|                       {{ selectedCustomer.shipping_address.city.name }}, {{ selectedCustomer.shipping_address.state.name }} {{ selectedCustomer.shipping_address.zip }} | ||||
|                     </label> | ||||
|                     <label class="country">{{ selectedCustomer.shipping_address.country.name }}</label> | ||||
|                     <label class="phone">{{ selectedCustomer.shipping_address.phone }}</label> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="customer-content"> | ||||
|               <label class="email">{{ selectedCustomer.email ? selectedCustomer.email : selectedCustomer.name }}</label> | ||||
|               <label class="action" @click="removeCustomer">{{ $t('general.remove') }}</label> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
|           <base-popup v-else class="add-customer"> | ||||
|             <div slot="activator" class="add-customer-action"> | ||||
|               <font-awesome-icon icon="user" class="customer-icon"/> | ||||
|               <label>{{ $t('customers.new_customer') }}<span class="text-danger"> * </span></label> | ||||
|             </div> | ||||
|             <customer-select /> | ||||
|           </base-popup> | ||||
|         </div> | ||||
|         <div class="col invoice-input"> | ||||
|           <div class="row mb-3"> | ||||
|             <div class="col"> | ||||
|               <label>{{ $t('invoices.invoice_date') }}<span class="text-danger"> * </span></label> | ||||
|               <base-date-picker | ||||
|                 v-model="newInvoice.invoice_date" | ||||
|                 :calendar-button="true" | ||||
|                 calendar-button-icon="calendar" | ||||
|                 @change="$v.newInvoice.invoice_date.$touch()" | ||||
|               /> | ||||
|               <span v-if="$v.newInvoice.invoice_date.$error && !$v.newInvoice.invoice_date.required" class="text-danger"> {{ $t('validation.required') }} </span> | ||||
|             </div> | ||||
|             <div class="col"> | ||||
|               <label>{{ $t('invoices.due_date') }}<span class="text-danger"> * </span></label> | ||||
|               <base-date-picker | ||||
|                 v-model="newInvoice.due_date" | ||||
|                 :invalid="$v.newInvoice.due_date.$error" | ||||
|                 :calendar-button="true" | ||||
|                 calendar-button-icon="calendar" | ||||
|                 @change="$v.newInvoice.due_date.$touch()" | ||||
|               /> | ||||
|               <span v-if="$v.newInvoice.due_date.$error && !$v.newInvoice.due_date.required" class="text-danger mt-1"> {{ $t('validation.required') }}</span> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="row mt-4"> | ||||
|             <div class="col"> | ||||
|               <label>{{ $t('invoices.invoice_number') }}<span class="text-danger"> * </span></label> | ||||
|               <base-input | ||||
|                 :invalid="$v.newInvoice.invoice_number.$error" | ||||
|                 :read-only="true" | ||||
|                 v-model="newInvoice.invoice_number" | ||||
|                 icon="hashtag" | ||||
|                 @input="$v.newInvoice.invoice_number.$touch()" | ||||
|               /> | ||||
|               <span v-show="$v.newInvoice.invoice_number.$error && !$v.newInvoice.invoice_number.required" class="text-danger mt-1"> {{ $tc('validation.required') }}  </span> | ||||
|             </div> | ||||
|             <div class="col"> | ||||
|               <label>{{ $t('invoices.ref_number') }}</label> | ||||
|               <base-input icon="hashtag" type="number"/> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <table class="item-table"> | ||||
|         <colgroup> | ||||
|           <col style="width: 50%;"> | ||||
|           <col style="width: 10%;"> | ||||
|           <col style="width: 10%;"> | ||||
|           <col v-if="discountPerItem === 'YES'" style="width: 15%;"> | ||||
|           <col style="width: 15%;"> | ||||
|         </colgroup> | ||||
|         <thead class="item-table-header"> | ||||
|           <tr> | ||||
|             <th class="text-left"> | ||||
|               <span class="column-heading item-heading"> | ||||
|                 {{ $tc('items.item',2) }} | ||||
|               </span> | ||||
|             </th> | ||||
|             <th class="text-right"> | ||||
|               <span class="column-heading"> | ||||
|                 {{ $t('invoices.item.quantity') }} | ||||
|               </span> | ||||
|             </th> | ||||
|             <th class="text-left"> | ||||
|               <span class="column-heading"> | ||||
|                 {{ $t('invoices.item.price') }} | ||||
|               </span> | ||||
|             </th> | ||||
|             <th v-if="discountPerItem === 'YES'" class="text-right"> | ||||
|               <span class="column-heading"> | ||||
|                 {{ $t('invoices.item.discount') }} | ||||
|               </span> | ||||
|             </th> | ||||
|             <th class="text-right"> | ||||
|               <span class="column-heading amount-heading"> | ||||
|                 {{ $t('invoices.item.amount') }} | ||||
|               </span> | ||||
|             </th> | ||||
|           </tr> | ||||
|         </thead> | ||||
|         <tbody class="item-body"> | ||||
|           <invoice-item | ||||
|             v-for="(item, index) in newInvoice.items" | ||||
|             :key="'inv-item-' + item.id" | ||||
|             :index="index" | ||||
|             :item-data="item" | ||||
|             :currency="currency" | ||||
|             :tax-per-item="taxPerItem" | ||||
|             :discount-per-item="discountPerItem" | ||||
|             @remove="removeItem" | ||||
|             @update="updateItem" | ||||
|             @itemValidate="checkItemsData" | ||||
|           /> | ||||
|         </tbody> | ||||
|       </table> | ||||
|       <div class="add-item-action" @click="addItem"> | ||||
|         <font-awesome-icon icon="shopping-basket" class="mr-2"/> | ||||
|         {{ $t('invoices.add_item') }} | ||||
|       </div> | ||||
|  | ||||
|       <div class="invoice-foot"> | ||||
|         <div> | ||||
|           <label>{{ $t('invoices.notes') }}</label> | ||||
|           <base-text-area | ||||
|             v-model="newInvoice.notes" | ||||
|             rows="3" | ||||
|             cols="50" | ||||
|           /> | ||||
|           <label class="mt-3 mb-1 d-block">{{ $t('invoices.invoice_template') }} <span class="text-danger"> * </span></label> | ||||
|           <base-button class="btn-template" icon="pencil-alt" right-icon @click="openTemplateModal" > | ||||
|             <span class="mr-4"> {{ $t('invoices.invoice_template') }} {{ getTemplateId }} </span> | ||||
|           </base-button> | ||||
|         </div> | ||||
|  | ||||
|         <div class="invoice-total"> | ||||
|           <div class="section"> | ||||
|             <label class="invoice-label">{{ $t('invoices.sub_total') }}</label> | ||||
|             <label class="invoice-amount"> | ||||
|               <div v-html="$utils.formatMoney(subtotal, currency)" /> | ||||
|             </label> | ||||
|           </div> | ||||
|           <div v-for="tax in allTaxes" :key="tax.tax_type_id" class="section"> | ||||
|             <label class="invoice-label">{{ tax.name }} - {{ tax.percent }}% </label> | ||||
|             <label class="invoice-amount"> | ||||
|               <div v-html="$utils.formatMoney(tax.amount, currency)" /> | ||||
|             </label> | ||||
|           </div> | ||||
|           <div v-if="discountPerItem === 'NO' || discountPerItem === null" class="section mt-2"> | ||||
|             <label class="invoice-label">{{ $t('invoices.discount') }}</label> | ||||
|             <div | ||||
|               class="btn-group discount-drop-down" | ||||
|               role="group" | ||||
|             > | ||||
|               <base-input | ||||
|                 v-model="discount" | ||||
|                 input-class="item-discount" | ||||
|               /> | ||||
|               <v-dropdown :show-arrow="false"> | ||||
|                 <button | ||||
|                   slot="activator" | ||||
|                   type="button" | ||||
|                   class="btn item-dropdown dropdown-toggle" | ||||
|                   data-toggle="dropdown" | ||||
|                   aria-haspopup="true" | ||||
|                   aria-expanded="false" | ||||
|                 > | ||||
|                   {{ newInvoice.discount_type == 'fixed' ? currency.symbol : '%' }} | ||||
|                 </button> | ||||
|                 <v-dropdown-item> | ||||
|                   <a class="dropdown-item" href="#" @click.prevent="selectFixed"> | ||||
|                     {{ $t('general.fixed') }} | ||||
|                   </a> | ||||
|                 </v-dropdown-item> | ||||
|                 <v-dropdown-item> | ||||
|                   <a class="dropdown-item" href="#" @click.prevent="selectPercentage"> | ||||
|                     {{ $t('general.percentage') }} | ||||
|                   </a> | ||||
|                 </v-dropdown-item> | ||||
|               </v-dropdown> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
|           <div v-if="taxPerItem === 'NO' || taxPerItem === null"> | ||||
|             <tax | ||||
|               v-for="(tax, index) in newInvoice.taxes" | ||||
|               :index="index" | ||||
|               :total="subtotalWithDiscount" | ||||
|               :key="tax.taxKey" | ||||
|               :tax="tax" | ||||
|               :taxes="newInvoice.taxes" | ||||
|               :currency="currency" | ||||
|               :total-tax="totalSimpleTax" | ||||
|               @remove="removeInvoiceTax" | ||||
|               @update="updateTax" | ||||
|             /> | ||||
|           </div> | ||||
|  | ||||
|           <base-popup v-if="taxPerItem === 'NO' || taxPerItem === null" ref="taxModal" class="tax-selector"> | ||||
|             <div slot="activator" class="float-right"> | ||||
|               + {{ $t('invoices.add_tax') }} | ||||
|             </div> | ||||
|             <tax-select @select="onSelectTax"/> | ||||
|           </base-popup> | ||||
|  | ||||
|           <div class="section border-top mt-3"> | ||||
|             <label class="invoice-label">{{ $t('invoices.total') }} {{ $t('invoices.amount') }}:</label> | ||||
|             <label class="invoice-amount total"> | ||||
|               <div v-html="$utils.formatMoney(total, currency)" /> | ||||
|             </label> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </form> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|  | ||||
| import MultiSelect from 'vue-multiselect' | ||||
| import InvoiceItem from './Item' | ||||
| import InvoiceStub from '../../stub/invoice' | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
| import moment from 'moment' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import Guid from 'guid' | ||||
| import TaxStub from '../../stub/tax' | ||||
| import Tax from './InvoiceTax' | ||||
| const { required } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     InvoiceItem, | ||||
|     MultiSelect, | ||||
|     Tax | ||||
|   }, | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       newInvoice: { | ||||
|         invoice_date: null, | ||||
|         due_date: null, | ||||
|         invoice_number: null, | ||||
|         user_id: null, | ||||
|         invoice_template_id: 1, | ||||
|         sub_total: null, | ||||
|         total: null, | ||||
|         tax: null, | ||||
|         notes: null, | ||||
|         discount_type: 'fixed', | ||||
|         discount_val: 0, | ||||
|         discount: 0, | ||||
|         items: [{ | ||||
|           ...InvoiceStub, | ||||
|           id: 1 | ||||
|         }], | ||||
|         taxes: [] | ||||
|       }, | ||||
|       invoiceTemplates: [], | ||||
|       selectedCurrency: '', | ||||
|       newItem: { | ||||
|         ...InvoiceStub | ||||
|       }, | ||||
|       taxPerItem: null, | ||||
|       discountPerItem: null, | ||||
|       isLoading: false | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     newInvoice: { | ||||
|       invoice_date: { | ||||
|         required | ||||
|       }, | ||||
|       due_date: { | ||||
|         required | ||||
|       }, | ||||
|       invoice_number: { | ||||
|         required | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('currency', [ | ||||
|       'defaultCurrency' | ||||
|     ]), | ||||
|     currency () { | ||||
|       return this.selectedCurrency | ||||
|     }, | ||||
|     subtotalWithDiscount () { | ||||
|       return this.subtotal - this.newInvoice.discount_val | ||||
|     }, | ||||
|     total () { | ||||
|       return this.subtotalWithDiscount + this.totalTax | ||||
|     }, | ||||
|     subtotal () { | ||||
|       return this.newInvoice.items.reduce(function (a, b) { | ||||
|         return a + b['total'] | ||||
|       }, 0) | ||||
|     }, | ||||
|     discount: { | ||||
|       get: function () { | ||||
|         return this.newInvoice.discount | ||||
|       }, | ||||
|       set: function (newValue) { | ||||
|         if (this.newInvoice.discount_type === 'percentage') { | ||||
|           this.newInvoice.discount_val = (this.subtotal * newValue) / 100 | ||||
|         } else { | ||||
|           this.newInvoice.discount_val = newValue * 100 | ||||
|         } | ||||
|  | ||||
|         this.newInvoice.discount = newValue | ||||
|       } | ||||
|     }, | ||||
|     totalSimpleTax () { | ||||
|       return window._.sumBy(this.newInvoice.taxes, function (tax) { | ||||
|         if (!tax.compound_tax) { | ||||
|           return tax.amount | ||||
|         } | ||||
|  | ||||
|         return 0 | ||||
|       }) | ||||
|     }, | ||||
|  | ||||
|     totalCompoundTax () { | ||||
|       return window._.sumBy(this.newInvoice.taxes, function (tax) { | ||||
|         if (tax.compound_tax) { | ||||
|           return tax.amount | ||||
|         } | ||||
|  | ||||
|         return 0 | ||||
|       }) | ||||
|     }, | ||||
|     totalTax () { | ||||
|       if (this.taxPerItem === 'NO' || this.taxPerItem === null) { | ||||
|         return this.totalSimpleTax + this.totalCompoundTax | ||||
|       } | ||||
|  | ||||
|       return window._.sumBy(this.newInvoice.items, function (tax) { | ||||
|         return tax.totalTax | ||||
|       }) | ||||
|     }, | ||||
|     allTaxes () { | ||||
|       let taxes = [] | ||||
|  | ||||
|       this.newInvoice.items.forEach((item) => { | ||||
|         item.taxes.forEach((tax) => { | ||||
|           let found = taxes.find((_tax) => { | ||||
|             return _tax.tax_type_id === tax.tax_type_id | ||||
|           }) | ||||
|  | ||||
|           if (found) { | ||||
|             found.amount += tax.amount | ||||
|           } else if (tax.tax_type_id) { | ||||
|             taxes.push({ | ||||
|               tax_type_id: tax.tax_type_id, | ||||
|               amount: tax.amount, | ||||
|               percent: tax.percent, | ||||
|               name: tax.name | ||||
|             }) | ||||
|           } | ||||
|         }) | ||||
|       }) | ||||
|  | ||||
|       return taxes | ||||
|     }, | ||||
|     ...mapGetters('customer', [ | ||||
|       'selectedCustomer' | ||||
|     ]), | ||||
|     ...mapGetters('invoice', [ | ||||
|       'getTemplateId' | ||||
|     ]), | ||||
|     ...mapGetters('general', [ | ||||
|       'itemDiscount' | ||||
|     ]) | ||||
|   }, | ||||
|   watch: { | ||||
|     selectedCustomer (newVal) { | ||||
|  | ||||
|       if (newVal.currency !== null) { | ||||
|         this.selectedCurrency = newVal.currency | ||||
|       } else { | ||||
|         this.selectedCurrency = this.defaultCurrency | ||||
|       } | ||||
|     }, | ||||
|     subtotal (newValue) { | ||||
|       if (this.newInvoice.discount_type === 'percentage') { | ||||
|         this.newInvoice.discount_val = (this.newInvoice.discount * newValue) / 100 | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     this.$nextTick(() => { | ||||
|       this.loadData() | ||||
|     }) | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('modal', [ | ||||
|       'openModal' | ||||
|     ]), | ||||
|     ...mapActions('customer', [ | ||||
|       'resetSelectedCustomer' | ||||
|     ]), | ||||
|     ...mapActions('taxType', { | ||||
|       loadTaxTypes: 'indexLoadData' | ||||
|     }), | ||||
|     ...mapActions('invoice', [ | ||||
|       'addInvoice', | ||||
|       'fetchInvoice' | ||||
|     ]), | ||||
|     selectFixed () { | ||||
|       if (this.newInvoice.discount_type === 'fixed') { | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       this.newInvoice.discount_val = this.newInvoice.discount * 100 | ||||
|       this.newInvoice.discount_type = 'fixed' | ||||
|     }, | ||||
|     selectPercentage () { | ||||
|       if (this.newInvoice.discount_type === 'percentage') { | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       this.newInvoice.discount_val = (this.subtotal * this.newInvoice.discount) / 100 | ||||
|  | ||||
|       this.newInvoice.discount_type = 'percentage' | ||||
|     }, | ||||
|     updateTax (data) { | ||||
|       Object.assign(this.newInvoice.taxes[data.index], {...data.item}) | ||||
|     }, | ||||
|     async loadData () { | ||||
|       let response = await this.fetchInvoice(this.$route.params.id) | ||||
|  | ||||
|       this.loadTaxTypes() | ||||
|  | ||||
|       if (response.data) { | ||||
|         this.newInvoice = response.data.invoice | ||||
|         this.discountPerItem = response.data.discount_per_item | ||||
|         this.taxPerItem = response.data.tax_per_item | ||||
|         this.selectedCurrency = this.defaultCurrency | ||||
|         this.invoiceTemplates = response.data.invoiceTemplates | ||||
|       } | ||||
|     }, | ||||
|     removeCustomer () { | ||||
|       this.resetSelectedCustomer() | ||||
|     }, | ||||
|     openTemplateModal () { | ||||
|       this.openModal({ | ||||
|         'title': 'Choose a template', | ||||
|         'componentName': 'InvoiceTemplate', | ||||
|         'data': this.invoiceTemplates | ||||
|       }) | ||||
|     }, | ||||
|     addItem () { | ||||
|       this.newInvoice.items.push({...this.newItem, id: (this.newInvoice.items.length + 1)}) | ||||
|     }, | ||||
|     removeItem (index) { | ||||
|       this.newInvoice.items.splice(index, 1) | ||||
|     }, | ||||
|     updateItem (data) { | ||||
|       Object.assign(this.newInvoice.items[data.index], {...data.item}) | ||||
|     }, | ||||
|     async submitInvoiceData () { | ||||
|       if (!this.checkValid()) { | ||||
|         return false | ||||
|       } | ||||
|  | ||||
|       this.isLoading = true | ||||
|  | ||||
|       let data = { | ||||
|         ...this.newInvoice, | ||||
|         invoice_date: moment(this.newInvoice.invoice_date).format('DD/MM/YYYY'), | ||||
|         due_date: moment(this.newInvoice.due_date).format('DD/MM/YYYY'), | ||||
|         sub_total: this.subtotal, | ||||
|         total: this.total, | ||||
|         tax: this.totalTax, | ||||
|         user_id: null, | ||||
|         invoice_template_id: this.getTemplateId | ||||
|       } | ||||
|  | ||||
|       if (this.selectedCustomer != null) { | ||||
|         data.user_id = this.selectedCustomer.id | ||||
|       } | ||||
|       let response = await this.addInvoice(data) | ||||
|  | ||||
|       if (response.data) { | ||||
|         window.toastr['success'](this.$t('invoices.created_message')) | ||||
|         this.isLoading = false | ||||
|         this.$route.push('/admin/invoices') | ||||
|       } | ||||
|     }, | ||||
|     checkItemsData (index, isValid) { | ||||
|       this.newInvoice.items[index].valid = isValid | ||||
|     }, | ||||
|     onSelectTax (selectedTax) { | ||||
|       let amount = 0 | ||||
|  | ||||
|       if (selectedTax.compound_tax && this.subtotalWithDiscount) { | ||||
|         amount = ((this.subtotalWithDiscount + this.totalSimpleTax) * selectedTax.percent) / 100 | ||||
|       } else if (this.subtotalWithDiscount && selectedTax.percent) { | ||||
|         amount = (this.subtotalWithDiscount * selectedTax.percent) / 100 | ||||
|       } | ||||
|  | ||||
|       this.newInvoice.taxes.push({ | ||||
|         ...TaxStub, | ||||
|         taxKey: Guid.raw(), | ||||
|         name: selectedTax.name, | ||||
|         percent: selectedTax.percent, | ||||
|         compound_tax: selectedTax.compound_tax, | ||||
|         tax_type_id: selectedTax.id, | ||||
|         amount | ||||
|       }) | ||||
|  | ||||
|       this.$refs.taxModal.close() | ||||
|     }, | ||||
|     removeInvoiceTax (index) { | ||||
|       this.newInvoice.taxes.splice(index, 1) | ||||
|     }, | ||||
|     checkValid () { | ||||
|       this.$v.newInvoice.$touch() | ||||
|       window.hub.$emit('checkItems') | ||||
|       let isValid = true | ||||
|       this.newInvoice.items.forEach((item) => { | ||||
|         if (!item.valid) { | ||||
|           isValid = false | ||||
|         } | ||||
|       }) | ||||
|       if (this.$v.newInvoice.$invalid === false && isValid === true) { | ||||
|         return true | ||||
|       } | ||||
|       return false | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										540
									
								
								resources/assets/js/views/invoices/Index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										540
									
								
								resources/assets/js/views/invoices/Index.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,540 @@ | ||||
| <template> | ||||
|   <div class="invoice-index-page invoices main-content"> | ||||
|     <div class="page-header"> | ||||
|       <h3 class="page-title"> {{ $t('invoices.title') }}</h3> | ||||
|       <ol class="breadcrumb"> | ||||
|         <li class="breadcrumb-item"> | ||||
|           <router-link | ||||
|             slot="item-title" | ||||
|             to="dashboard"> | ||||
|             {{ $t('general.home') }} | ||||
|           </router-link> | ||||
|         </li> | ||||
|         <li class="breadcrumb-item"> | ||||
|           <router-link | ||||
|             slot="item-title" | ||||
|             to="#"> | ||||
|             {{ $tc('invoices.invoice', 2) }} | ||||
|           </router-link> | ||||
|         </li> | ||||
|       </ol> | ||||
|       <div class="page-actions row"> | ||||
|         <div class="col-xs-2 mr-4"> | ||||
|           <base-button | ||||
|             v-show="totalInvoices || filtersApplied" | ||||
|             :outline="true" | ||||
|             :icon="filterIcon" | ||||
|             size="large" | ||||
|             color="theme" | ||||
|             right-icon | ||||
|             @click="toggleFilter" | ||||
|           > | ||||
|             {{ $t('general.filter') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|         <router-link slot="item-title" class="col-xs-2" to="/admin/invoices/create"> | ||||
|           <base-button size="large" icon="plus" color="theme"> | ||||
|             {{ $t('invoices.new_invoice') }} | ||||
|           </base-button> | ||||
|         </router-link> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <transition name="fade"> | ||||
|       <div v-show="showFilters" class="filter-section"> | ||||
|         <div class="filter-container"> | ||||
|           <div class="filter-customer"> | ||||
|             <label>{{ $tc('customers.customer',1) }} </label> | ||||
|             <base-customer-select | ||||
|               ref="customerSelect" | ||||
|               @select="onSelectCustomer" | ||||
|               @deselect="clearCustomerSearch" | ||||
|             /> | ||||
|           </div> | ||||
|           <div class="filter-status"> | ||||
|             <label>{{ $t('invoices.status') }}</label> | ||||
|             <base-select | ||||
|               v-model="filters.status" | ||||
|               :options="status" | ||||
|               :group-select="false" | ||||
|               :searchable="true" | ||||
|               :show-labels="false" | ||||
|               :placeholder="$t('general.select_a_status')" | ||||
|               group-values="options" | ||||
|               group-label="label" | ||||
|               track-by="name" | ||||
|               label="name" | ||||
|               @remove="clearStatusSearch()" | ||||
|             /> | ||||
|           </div> | ||||
|           <div class="filter-date"> | ||||
|             <div class="from pr-3"> | ||||
|               <label>{{ $t('general.from') }}</label> | ||||
|               <base-date-picker | ||||
|                 v-model="filters.from_date" | ||||
|                 :calendar-button="true" | ||||
|                 calendar-button-icon="calendar" | ||||
|               /> | ||||
|             </div> | ||||
|             <div class="dashed" /> | ||||
|             <div class="to pl-3"> | ||||
|               <label>{{ $t('general.to') }}</label> | ||||
|               <base-date-picker | ||||
|                 v-model="filters.to_date" | ||||
|                 :calendar-button="true" | ||||
|                 calendar-button-icon="calendar" | ||||
|               /> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="filter-invoice"> | ||||
|             <label>{{ $t('invoices.invoice_number') }}</label> | ||||
|             <base-input | ||||
|               v-model="filters.invoice_number" | ||||
|               icon="hashtag"/> | ||||
|           </div> | ||||
|         </div> | ||||
|         <label class="clear-filter" @click="clearFilter">{{ $t('general.clear_all') }}</label> | ||||
|       </div> | ||||
|     </transition> | ||||
|  | ||||
|     <div v-cloak v-show="showEmptyScreen" class="col-xs-1 no-data-info" align="center"> | ||||
|       <moon-walker-icon class="mt-5 mb-4"/> | ||||
|       <div class="row" align="center"> | ||||
|         <label class="col title">{{ $t('invoices.no_invoices') }}</label> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <label class="description col mt-1" align="center">{{ $t('invoices.list_of_invoices') }}</label> | ||||
|       </div> | ||||
|       <div class="btn-container"> | ||||
|         <base-button | ||||
|           :outline="true" | ||||
|           color="theme" | ||||
|           class="mt-3" | ||||
|           size="large" | ||||
|           @click="$router.push('invoices/create')" | ||||
|         > | ||||
|           {{ $t('invoices.new_invoice') }} | ||||
|         </base-button> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <div v-show="!showEmptyScreen" class="table-container"> | ||||
|       <div class="table-actions mt-5"> | ||||
|         <p class="table-stats">{{ $t('general.showing') }}: <b>{{ invoices.length }}</b> {{ $t('general.of') }} <b>{{ totalInvoices }}</b></p> | ||||
|  | ||||
|         <!-- Tabs --> | ||||
|         <ul class="tabs"> | ||||
|           <li class="tab" @click="getStatus('UNPAID')"> | ||||
|             <a :class="['tab-link', {'a-active': filters.status.value === 'UNPAID'}]" href="#" >{{ $t('general.due') }}</a> | ||||
|           </li> | ||||
|           <li class="tab" @click="getStatus('DRAFT')"> | ||||
|             <a :class="['tab-link', {'a-active': filters.status.value === 'DRAFT'}]" href="#">{{ $t('general.draft') }}</a> | ||||
|           </li> | ||||
|           <li class="tab" @click="getStatus('')"> | ||||
|             <a :class="['tab-link', {'a-active': filters.status.value === '' || filters.status.value === null || filters.status.value !== 'DRAFT' && filters.status.value !== 'UNPAID'}]" href="#">{{ $t('general.all') }}</a> | ||||
|           </li> | ||||
|         </ul> | ||||
|         <transition name="fade"> | ||||
|           <v-dropdown v-if="selectedInvoices.length" :show-arrow="false"> | ||||
|             <span slot="activator" href="#" class="table-actions-button dropdown-toggle"> | ||||
|               {{ $t('general.actions') }} | ||||
|             </span> | ||||
|             <v-dropdown-item> | ||||
|               <div class="dropdown-item" @click="removeMultipleInvoices"> | ||||
|                 <font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" /> | ||||
|                 {{ $t('general.delete') }} | ||||
|               </div> | ||||
|             </v-dropdown-item> | ||||
|           </v-dropdown> | ||||
|         </transition> | ||||
|       </div> | ||||
|       <div class="custom-control custom-checkbox"> | ||||
|         <input | ||||
|           id="select-all" | ||||
|           v-model="selectAllFieldStatus" | ||||
|           type="checkbox" | ||||
|           class="custom-control-input" | ||||
|           @change="selectAllInvoices" | ||||
|         > | ||||
|         <label v-show="!isRequestOngoing" for="select-all" class="custom-control-label selectall"> | ||||
|           <span class="select-all-label">{{ $t('general.select_all') }} </span> | ||||
|         </label> | ||||
|       </div> | ||||
|  | ||||
|       <table-component | ||||
|         ref="table" | ||||
|         :show-filter="false" | ||||
|         :data="fetchData" | ||||
|         table-class="table" | ||||
|       > | ||||
|         <table-column | ||||
|           :sortable="false" | ||||
|           :filterable="false" | ||||
|           cell-class="no-click" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <div class="custom-control custom-checkbox"> | ||||
|               <input | ||||
|                 :id="row.id" | ||||
|                 v-model="selectField" | ||||
|                 :value="row.id" | ||||
|                 type="checkbox" | ||||
|                 class="custom-control-input" | ||||
|               > | ||||
|               <label :for="row.id" class="custom-control-label"/> | ||||
|             </div> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <table-column | ||||
|           :label="$t('invoices.date')" | ||||
|           sort-as="invoice_date" | ||||
|           show="formattedInvoiceDate" | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('invoices.customer')" | ||||
|           width="20%" | ||||
|           show="name" | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('invoices.status')" | ||||
|           sort-as="status" | ||||
|         > | ||||
|           <template slot-scope="row" > | ||||
|             <span> {{ $t('invoices.status') }}</span> | ||||
|             <span :class="'inv-status-'+row.status.toLowerCase()">{{ (row.status != 'PARTIALLY_PAID')? row.status : row.status.replace('_', ' ') }}</span> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <table-column | ||||
|           :label="$t('invoices.paid_status')" | ||||
|           sort-as="paid_status" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <span>{{ $t('invoices.paid_status') }}</span> | ||||
|             <span :class="'inv-status-'+row.paid_status.toLowerCase()">{{ (row.paid_status != 'PARTIALLY_PAID')? row.paid_status : row.paid_status.replace('_', ' ') }}</span> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <table-column | ||||
|           :label="$t('invoices.number')" | ||||
|           show="invoice_number" | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('invoices.amount_due')" | ||||
|           sort-as="due_amount" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <span>{{ $t('invoices.amount_due') }}</span> | ||||
|             <div v-html="$utils.formatMoney(row.due_amount, row.user.currency)"/> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <table-column | ||||
|           :sortable="false" | ||||
|           :filterable="false" | ||||
|           cell-class="action-dropdown no-click" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <span>{{ $t('invoices.action') }}</span> | ||||
|             <v-dropdown> | ||||
|               <a slot="activator" href="#"> | ||||
|                 <dot-icon /> | ||||
|               </a> | ||||
|               <v-dropdown-item> | ||||
|                 <router-link :to="{path: `invoices/${row.id}/edit`}" class="dropdown-item"> | ||||
|                   <font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon"/> | ||||
|                   {{ $t('general.edit') }} | ||||
|                 </router-link> | ||||
|                 <router-link :to="{path: `invoices/${row.id}/view`}" class="dropdown-item"> | ||||
|                   <font-awesome-icon icon="eye" class="dropdown-item-icon" /> | ||||
|                   {{ $t('invoices.view') }} | ||||
|                 </router-link> | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item> | ||||
|                 <a class="dropdown-item" href="#" @click="sendInvoice(row.id)" > | ||||
|                   <font-awesome-icon icon="paper-plane" class="dropdown-item-icon" /> | ||||
|                   {{ $t('invoices.send_invoice') }} | ||||
|                 </a> | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item v-if="row.status === 'DRAFT'"> | ||||
|                 <a class="dropdown-item" href="#" @click="sentInvoice(row.id)"> | ||||
|                   <font-awesome-icon icon="check-circle" class="dropdown-item-icon" /> | ||||
|                   {{ $t('invoices.mark_as_sent') }} | ||||
|                 </a> | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item> | ||||
|                 <div class="dropdown-item" @click="removeInvoice(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' | ||||
| import MoonWalkerIcon from '../../../js/components/icon/MoonwalkerIcon' | ||||
| import moment from 'moment' | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     'moon-walker-icon': MoonWalkerIcon | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       showFilters: false, | ||||
|       currency: null, | ||||
|       status: [ | ||||
|         { | ||||
|           label: 'Status', | ||||
|           isDisable: true, | ||||
|           options: [ | ||||
|             { name: 'DRAFT', value: 'DRAFT' }, | ||||
|             { name: 'DUE', value: 'UNPAID' }, | ||||
|             { name: 'SENT', value: 'SENT' }, | ||||
|             { name: 'VIEWED', value: 'VIEWED' }, | ||||
|             { name: 'OVERDUE', value: 'OVERDUE' }, | ||||
|             { name: 'COMPLETED', value: 'COMPLETED' } | ||||
|           ] | ||||
|         }, | ||||
|         { | ||||
|           label: 'Paid Status', | ||||
|           options: [ | ||||
|             { name: 'UNPAID', value: 'UNPAID' }, | ||||
|             { name: 'PAID', value: 'PAID' }, | ||||
|             { name: 'PARTIALLY PAID', value: 'PARTIALLY_PAID' } | ||||
|           ] | ||||
|         } | ||||
|       ], | ||||
|       filtersApplied: false, | ||||
|       isRequestOngoing: true, | ||||
|       filters: { | ||||
|         customer: '', | ||||
|         status: { name: 'DUE', value: 'UNPAID' }, | ||||
|         from_date: '', | ||||
|         to_date: '', | ||||
|         invoice_number: '' | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   computed: { | ||||
|     showEmptyScreen () { | ||||
|       return !this.totalInvoices && !this.isRequestOngoing && !this.filtersApplied | ||||
|     }, | ||||
|     filterIcon () { | ||||
|       return (this.showFilters) ? 'times' : 'filter' | ||||
|     }, | ||||
|     ...mapGetters('customer', [ | ||||
|       'customers' | ||||
|     ]), | ||||
|     ...mapGetters('invoice', [ | ||||
|       'selectedInvoices', | ||||
|       'totalInvoices', | ||||
|       'invoices', | ||||
|       'selectAllField' | ||||
|     ]), | ||||
|     selectField: { | ||||
|       get: function () { | ||||
|         return this.selectedInvoices | ||||
|       }, | ||||
|       set: function (val) { | ||||
|         this.selectInvoice(val) | ||||
|       } | ||||
|     }, | ||||
|     selectAllFieldStatus: { | ||||
|       get: function () { | ||||
|         return this.selectAllField | ||||
|       }, | ||||
|       set: function (val) { | ||||
|         this.setSelectAllState(val) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     filters: { | ||||
|       handler: 'setFilters', | ||||
|       deep: true | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     this.fetchCustomers() | ||||
|   }, | ||||
|   destroyed () { | ||||
|     if (this.selectAllField) { | ||||
|       this.selectAllInvoices() | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('invoice', [ | ||||
|       'fetchInvoices', | ||||
|       'getRecord', | ||||
|       'selectInvoice', | ||||
|       'resetSelectedInvoices', | ||||
|       'selectAllInvoices', | ||||
|       'deleteInvoice', | ||||
|       'deleteMultipleInvoices', | ||||
|       'sendEmail', | ||||
|       'markAsSent', | ||||
|       'setSelectAllState' | ||||
|     ]), | ||||
|     ...mapActions('customer', [ | ||||
|       'fetchCustomers' | ||||
|     ]), | ||||
|     async sendInvoice (id) { | ||||
|       const data = { | ||||
|         id: id | ||||
|       } | ||||
|       let response = await this.sendEmail(data) | ||||
|       this.refreshTable() | ||||
|       if (response.data) { | ||||
|         window.toastr['success'](this.$tc('invoices.send_invoice')) | ||||
|       } | ||||
|     }, | ||||
|     async sentInvoice (id) { | ||||
|       const data = { | ||||
|         id: id | ||||
|       } | ||||
|       let response = await this.markAsSent(data) | ||||
|       this.refreshTable() | ||||
|       if (response.data) { | ||||
|         window.toastr['success'](this.$tc('invoices.mark_as_sent')) | ||||
|       } | ||||
|     }, | ||||
|     getStatus (val) { | ||||
|       this.filters.status = { | ||||
|         name: val, | ||||
|         value: val | ||||
|       } | ||||
|     }, | ||||
|     refreshTable () { | ||||
|       this.$refs.table.refresh() | ||||
|     }, | ||||
|     async fetchData ({ page, filter, sort }) { | ||||
|       let data = { | ||||
|         customer_id: this.filters.customer === '' ? this.filters.customer : this.filters.customer.id, | ||||
|         status: this.filters.status.value, | ||||
|         from_date: this.filters.from_date === '' ? this.filters.from_date : moment(this.filters.from_date).format('DD/MM/YYYY'), | ||||
|         to_date: this.filters.to_date === '' ? this.filters.to_date : moment(this.filters.to_date).format('DD/MM/YYYY'), | ||||
|         invoice_number: this.filters.invoice_number, | ||||
|         orderByField: sort.fieldName || 'created_at', | ||||
|         orderBy: sort.order || 'desc', | ||||
|         page | ||||
|       } | ||||
|  | ||||
|       this.isRequestOngoing = true | ||||
|       let response = await this.fetchInvoices(data) | ||||
|       this.isRequestOngoing = false | ||||
|  | ||||
|       this.currency = response.data.currency | ||||
|  | ||||
|       return { | ||||
|         data: response.data.invoices.data, | ||||
|         pagination: { | ||||
|           totalPages: response.data.invoices.last_page, | ||||
|           currentPage: page, | ||||
|           count: response.data.invoices.count | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     setFilters () { | ||||
|       this.filtersApplied = true | ||||
|       this.resetSelectedInvoices() | ||||
|       this.refreshTable() | ||||
|     }, | ||||
|     clearFilter () { | ||||
|       if (this.filters.customer) { | ||||
|         this.$refs.customerSelect.$refs.baseSelect.removeElement(this.filters.customer) | ||||
|       } | ||||
|       this.filters = { | ||||
|         customer: '', | ||||
|         status: '', | ||||
|         from_date: '', | ||||
|         to_date: '', | ||||
|         invoice_number: '' | ||||
|       } | ||||
|  | ||||
|       this.$nextTick(() => { | ||||
|         this.filtersApplied = false | ||||
|       }) | ||||
|     }, | ||||
|     toggleFilter () { | ||||
|       if (this.showFilters && this.filtersApplied) { | ||||
|         this.clearFilter() | ||||
|         this.refreshTable() | ||||
|       } | ||||
|  | ||||
|       this.showFilters = !this.showFilters | ||||
|     }, | ||||
|     onSelectCustomer (customer) { | ||||
|       this.filters.customer = customer | ||||
|     }, | ||||
|     async removeInvoice (id) { | ||||
|       this.id = id | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$tc('invoices.confirm_delete'), | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let res = await this.deleteInvoice(this.id) | ||||
|  | ||||
|           if (res.data.success) { | ||||
|             window.toastr['success'](this.$tc('invoices.deleted_message')) | ||||
|             return true | ||||
|           } | ||||
|  | ||||
|           if (res.data.error === 'payment_attached') { | ||||
|             window.toastr['error'](this.$t('invoices.payment_attached_message'), this.$t('general.action_failed')) | ||||
|             return true | ||||
|           } | ||||
|  | ||||
|           window.toastr['error'](res.data.error) | ||||
|           return true | ||||
|         } | ||||
|  | ||||
|         this.$refs.table.refresh() | ||||
|         this.filtersApplied = false | ||||
|         this.resetSelectedInvoices() | ||||
|       }) | ||||
|     }, | ||||
|     async removeMultipleInvoices () { | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$tc('invoices.confirm_delete', 2), | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let res = await this.deleteMultipleInvoices() | ||||
|           if (res.data.error === 'payment_attached') { | ||||
|             window.toastr['error'](this.$t('invoices.payment_attached_message'), this.$t('general.action_failed')) | ||||
|             return true | ||||
|           } | ||||
|           if (res.data) { | ||||
|             this.$refs.table.refresh() | ||||
|             this.filtersApplied = false | ||||
|             this.resetSelectedInvoices() | ||||
|             window.toastr['success'](this.$tc('invoices.deleted_message', 2)) | ||||
|           } else if (res.data.error) { | ||||
|             window.toastr['error'](res.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     async clearCustomerSearch (removedOption, id) { | ||||
|       this.filters.customer = '' | ||||
|       this.refreshTable() | ||||
|     }, | ||||
|     async clearStatusSearch (removedOption, id) { | ||||
|       this.filters.status = '' | ||||
|       this.refreshTable() | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										83
									
								
								resources/assets/js/views/invoices/InvoiceTax.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								resources/assets/js/views/invoices/InvoiceTax.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | ||||
| <template> | ||||
|   <div class="section mt-2"> | ||||
|     <label class="invoice-label"> | ||||
|       {{ tax.name }} ({{ tax.percent }}%) | ||||
|     </label> | ||||
|     <label class="invoice-amount"> | ||||
|       <div v-html="$utils.formatMoney(tax.amount, currency)" /> | ||||
|  | ||||
|       <font-awesome-icon | ||||
|         class="ml-2" | ||||
|         icon="trash-alt" | ||||
|         @click="$emit('remove', index)" | ||||
|       /> | ||||
|     </label> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|   props: { | ||||
|     index: { | ||||
|       type: Number, | ||||
|       required: true | ||||
|     }, | ||||
|     tax: { | ||||
|       type: Object, | ||||
|       required: true | ||||
|     }, | ||||
|     taxes: { | ||||
|       type: Array, | ||||
|       required: true | ||||
|     }, | ||||
|     total: { | ||||
|       type: Number, | ||||
|       default: 0 | ||||
|     }, | ||||
|     totalTax: { | ||||
|       type: Number, | ||||
|       default: 0 | ||||
|     }, | ||||
|     currency: { | ||||
|       type: [Object, String], | ||||
|       required: true | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     taxAmount () { | ||||
|       if (this.tax.compound_tax && this.total) { | ||||
|         return ((this.total + this.totalTax) * this.tax.percent) / 100 | ||||
|       } | ||||
|  | ||||
|       if (this.total && this.tax.percent) { | ||||
|         return (this.total * this.tax.percent) / 100 | ||||
|       } | ||||
|  | ||||
|       return 0 | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     total: { | ||||
|       handler: 'updateTax' | ||||
|     }, | ||||
|     totalTax: { | ||||
|       handler: 'updateTax' | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     updateTax () { | ||||
|       this.$emit('update', { | ||||
|         'index': this.index, | ||||
|         'item': { | ||||
|           ...this.tax, | ||||
|           amount: this.taxAmount | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|  | ||||
| </style> | ||||
							
								
								
									
										403
									
								
								resources/assets/js/views/invoices/Item.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										403
									
								
								resources/assets/js/views/invoices/Item.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,403 @@ | ||||
| <template> | ||||
|   <tr class="item-row invoice-item-row"> | ||||
|     <td colspan="5"> | ||||
|       <table class="full-width"> | ||||
|         <colgroup> | ||||
|           <col style="width: 40%;"> | ||||
|           <col style="width: 10%;"> | ||||
|           <col style="width: 15%;"> | ||||
|           <col v-if="discountPerItem === 'YES'" style="width: 15%;"> | ||||
|           <col style="width: 15%;"> | ||||
|         </colgroup> | ||||
|         <tbody> | ||||
|           <tr> | ||||
|             <td class=""> | ||||
|               <div class="item-select-wrapper"> | ||||
|                 <div class="sort-icon-wrapper handle"> | ||||
|                   <font-awesome-icon | ||||
|                     class="sort-icon" | ||||
|                     icon="grip-vertical" | ||||
|                   /> | ||||
|                 </div> | ||||
|                 <item-select | ||||
|                   ref="itemSelect" | ||||
|                   :invalid="$v.item.name.$error" | ||||
|                   :invalid-description="$v.item.description.$error" | ||||
|                   :item="item" | ||||
|                   @search="searchVal" | ||||
|                   @select="onSelectItem" | ||||
|                   @deselect="deselectItem" | ||||
|                   @onDesriptionInput="$v.item.description.$touch()" | ||||
|                 /> | ||||
|               </div> | ||||
|             </td> | ||||
|             <td class="text-right"> | ||||
|               <base-input | ||||
|                 v-model="item.quantity" | ||||
|                 :invalid="$v.item.quantity.$error" | ||||
|                 type="number" | ||||
|                 small | ||||
|                 @keyup="updateItem" | ||||
|                 @input="$v.item.quantity.$touch()" | ||||
|               /> | ||||
|               <div v-if="$v.item.quantity.$error"> | ||||
|                 <span v-if="!$v.item.quantity.maxLength" class="text-danger">{{ $t('validation.quantity_maxlength') }}</span> | ||||
|               </div> | ||||
|             </td> | ||||
|             <td class="text-left"> | ||||
|               <div class="d-flex flex-column"> | ||||
|                 <div class="flex-fillbd-highlight"> | ||||
|                   <div class="base-input"> | ||||
|                     <money | ||||
|                       v-model="price" | ||||
|                       v-bind="customerCurrency" | ||||
|                       class="input-field" | ||||
|                       @input="$v.item.price.$touch()" | ||||
|                     /> | ||||
|                   </div> | ||||
|                   <div v-if="$v.item.price.$error"> | ||||
|                     <span v-if="!$v.item.price.maxLength" class="text-danger">{{ $t('validation.price_maxlength') }}</span> | ||||
|                   </div> | ||||
|                 </div> | ||||
|  | ||||
|               </div> | ||||
|             </td> | ||||
|             <td v-if="discountPerItem === 'YES'" class=""> | ||||
|               <div class="d-flex flex-column bd-highlight"> | ||||
|                 <div | ||||
|                   class="btn-group flex-fill bd-highlight" | ||||
|                   role="group" | ||||
|                 > | ||||
|                   <base-input | ||||
|                     v-model="discount" | ||||
|                     :invalid="$v.item.discount_val.$error" | ||||
|                     input-class="item-discount" | ||||
|                     @input="$v.item.discount_val.$touch()" | ||||
|                   /> | ||||
|                   <v-dropdown :show-arrow="false" theme-light> | ||||
|                     <button | ||||
|                       slot="activator" | ||||
|                       type="button" | ||||
|                       class="btn item-dropdown dropdown-toggle" | ||||
|                       data-toggle="dropdown" | ||||
|                       aria-haspopup="true" | ||||
|                       aria-expanded="false" | ||||
|                     > | ||||
|                       {{ item.discount_type == 'fixed' ? currency.symbol : '%' }} | ||||
|                     </button> | ||||
|                     <v-dropdown-item> | ||||
|                       <a class="dropdown-item" href="#" @click.prevent="selectFixed" > | ||||
|                         {{ $t('general.fixed') }} | ||||
|                       </a> | ||||
|                     </v-dropdown-item> | ||||
|                     <v-dropdown-item> | ||||
|                       <a class="dropdown-item" href="#" @click.prevent="selectPercentage"> | ||||
|                         {{ $t('general.percentage') }} | ||||
|                       </a> | ||||
|                     </v-dropdown-item> | ||||
|                   </v-dropdown> | ||||
|                 </div> | ||||
|                 <!-- <div v-if="$v.item.discount.$error"> discount error </div> --> | ||||
|               </div> | ||||
|             </td> | ||||
|             <td class="text-right"> | ||||
|               <div class="item-amount"> | ||||
|                 <span> | ||||
|                   <div v-html="$utils.formatMoney(total, currency)" /> | ||||
|                 </span> | ||||
|  | ||||
|                 <div class="remove-icon-wrapper"> | ||||
|                   <font-awesome-icon | ||||
|                     v-if="index > 0" | ||||
|                     class="remove-icon" | ||||
|                     icon="trash-alt" | ||||
|                     @click="removeItem" | ||||
|                   /> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </td> | ||||
|           </tr> | ||||
|           <tr v-if="taxPerItem === 'YES'" class="tax-tr"> | ||||
|             <td /> | ||||
|             <td colspan="4"> | ||||
|               <tax | ||||
|                 v-for="(tax, index) in item.taxes" | ||||
|                 :key="tax.id" | ||||
|                 :index="index" | ||||
|                 :tax-data="tax" | ||||
|                 :taxes="item.taxes" | ||||
|                 :discounted-total="total" | ||||
|                 :total-tax="totalSimpleTax" | ||||
|                 :total="total" | ||||
|                 :currency="currency" | ||||
|                 @update="updateTax" | ||||
|                 @remove="removeTax" | ||||
|               /> | ||||
|             </td> | ||||
|           </tr> | ||||
|         </tbody> | ||||
|       </table> | ||||
|     </td> | ||||
|   </tr> | ||||
| </template> | ||||
| <script> | ||||
| import Guid from 'guid' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
| import TaxStub from '../../stub/tax' | ||||
| import InvoiceStub from '../../stub/invoice' | ||||
| import ItemSelect from './ItemSelect' | ||||
| import Tax from './Tax' | ||||
| const { required, minValue, between, maxLength } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     Tax, | ||||
|     ItemSelect | ||||
|   }, | ||||
|   mixins: [validationMixin], | ||||
|   props: { | ||||
|     itemData: { | ||||
|       type: Object, | ||||
|       default: null | ||||
|     }, | ||||
|     index: { | ||||
|       type: Number, | ||||
|       default: null | ||||
|     }, | ||||
|     type: { | ||||
|       type: String, | ||||
|       default: '' | ||||
|     }, | ||||
|     currency: { | ||||
|       type: [Object, String], | ||||
|       required: true | ||||
|     }, | ||||
|     taxPerItem: { | ||||
|       type: String, | ||||
|       default: '' | ||||
|     }, | ||||
|     discountPerItem: { | ||||
|       type: String, | ||||
|       default: '' | ||||
|     } | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       isClosePopup: false, | ||||
|       itemSelect: null, | ||||
|       item: {...this.itemData}, | ||||
|       maxDiscount: 0, | ||||
|       money: { | ||||
|         decimal: '.', | ||||
|         thousands: ',', | ||||
|         prefix: '$ ', | ||||
|         precision: 2, | ||||
|         masked: false | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('item', [ | ||||
|       'items' | ||||
|     ]), | ||||
|     ...mapGetters('currency', [ | ||||
|       'defaultCurrencyForInput' | ||||
|     ]), | ||||
|     customerCurrency () { | ||||
|       if (this.currency) { | ||||
|         return { | ||||
|           decimal: this.currency.decimal_separator, | ||||
|           thousands: this.currency.thousand_separator, | ||||
|           prefix: this.currency.symbol + ' ', | ||||
|           precision: this.currency.precision, | ||||
|           masked: false | ||||
|         } | ||||
|       } else { | ||||
|         return this.defaultCurrenctForInput | ||||
|       } | ||||
|     }, | ||||
|     subtotal () { | ||||
|       return this.item.price * this.item.quantity | ||||
|     }, | ||||
|     discount: { | ||||
|       get: function () { | ||||
|         return this.item.discount | ||||
|       }, | ||||
|       set: function (newValue) { | ||||
|         if (this.item.discount_type === 'percentage') { | ||||
|           this.item.discount_val = (this.subtotal * newValue) / 100 | ||||
|         } else { | ||||
|           this.item.discount_val = newValue * 100 | ||||
|         } | ||||
|  | ||||
|         this.item.discount = newValue | ||||
|       } | ||||
|     }, | ||||
|     total () { | ||||
|       return this.subtotal - this.item.discount_val | ||||
|     }, | ||||
|     totalSimpleTax () { | ||||
|       return window._.sumBy(this.item.taxes, function (tax) { | ||||
|         if (!tax.compound_tax) { | ||||
|           return tax.amount | ||||
|         } | ||||
|  | ||||
|         return 0 | ||||
|       }) | ||||
|     }, | ||||
|     totalCompoundTax () { | ||||
|       return window._.sumBy(this.item.taxes, function (tax) { | ||||
|         if (tax.compound_tax) { | ||||
|           return tax.amount | ||||
|         } | ||||
|  | ||||
|         return 0 | ||||
|       }) | ||||
|     }, | ||||
|     totalTax () { | ||||
|       return this.totalSimpleTax + this.totalCompoundTax | ||||
|     }, | ||||
|     price: { | ||||
|       get: function () { | ||||
|         if (parseFloat(this.item.price) > 0) { | ||||
|           return this.item.price / 100 | ||||
|         } | ||||
|  | ||||
|         return this.item.price | ||||
|       }, | ||||
|       set: function (newValue) { | ||||
|         if (parseFloat(newValue) > 0) { | ||||
|           this.item.price = newValue * 100 | ||||
|           this.maxDiscount = this.item.price | ||||
|         } else { | ||||
|           this.item.price = newValue | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     item: { | ||||
|       handler: 'updateItem', | ||||
|       deep: true | ||||
|     }, | ||||
|     subtotal (newValue) { | ||||
|       if (this.item.discount_type === 'percentage') { | ||||
|         this.item.discount_val = (this.item.discount * newValue) / 100 | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   validations () { | ||||
|     return { | ||||
|       item: { | ||||
|         name: { | ||||
|           required | ||||
|         }, | ||||
|         quantity: { | ||||
|           required, | ||||
|           minValue: minValue(1), | ||||
|           maxLength: maxLength(10) | ||||
|         }, | ||||
|         price: { | ||||
|           required, | ||||
|           minValue: minValue(1), | ||||
|           maxLength: maxLength(10) | ||||
|         }, | ||||
|         discount_val: { | ||||
|           between: between(0, this.maxDiscount) | ||||
|         }, | ||||
|         description: { | ||||
|           maxLength: maxLength(255) | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     window.hub.$on('checkItems', this.validateItem) | ||||
|     window.hub.$on('newItem', this.onSelectItem) | ||||
|   }, | ||||
|   methods: { | ||||
|     updateTax (data) { | ||||
|       this.$set(this.item.taxes, data.index, data.item) | ||||
|  | ||||
|       let lastTax = this.item.taxes[this.item.taxes.length - 1] | ||||
|  | ||||
|       if (lastTax.tax_type_id !== 0) { | ||||
|         this.item.taxes.push({...TaxStub, id: Guid.raw()}) | ||||
|       } | ||||
|  | ||||
|       this.updateItem() | ||||
|     }, | ||||
|     removeTax (index) { | ||||
|       this.item.taxes.splice(index, 1) | ||||
|  | ||||
|       this.updateItem() | ||||
|     }, | ||||
|     taxWithPercentage ({ name, percent }) { | ||||
|       return `${name} (${percent}%)` | ||||
|     }, | ||||
|     searchVal (val) { | ||||
|       this.item.name = val | ||||
|     }, | ||||
|     deselectItem () { | ||||
|       this.item = {...InvoiceStub, id: this.item.id} | ||||
|       this.$nextTick(() => { | ||||
|         this.$refs.itemSelect.$refs.baseSelect.$refs.search.focus() | ||||
|       }) | ||||
|     }, | ||||
|     onSelectItem (item) { | ||||
|       this.item.name = item.name | ||||
|       this.item.price = item.price | ||||
|       this.item.item_id = item.id | ||||
|       this.item.description = item.description | ||||
|  | ||||
|       // if (this.item.taxes.length) { | ||||
|       //   this.item.taxes = {...item.taxes} | ||||
|       // } | ||||
|     }, | ||||
|     selectFixed () { | ||||
|       if (this.item.discount_type === 'fixed') { | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       this.item.discount_val = this.item.discount * 100 | ||||
|       this.item.discount_type = 'fixed' | ||||
|     }, | ||||
|     selectPercentage () { | ||||
|       if (this.item.discount_type === 'percentage') { | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       this.item.discount_val = (this.subtotal * this.item.discount) / 100 | ||||
|  | ||||
|       this.item.discount_type = 'percentage' | ||||
|     }, | ||||
|     updateItem () { | ||||
|       this.$emit('update', { | ||||
|         'index': this.index, | ||||
|         'item': { | ||||
|           ...this.item, | ||||
|           total: this.total, | ||||
|           totalSimpleTax: this.totalSimpleTax, | ||||
|           totalCompoundTax: this.totalCompoundTax, | ||||
|           totalTax: this.totalTax, | ||||
|           tax: this.totalTax, | ||||
|           taxes: [...this.item.taxes] | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     removeItem () { | ||||
|       this.$emit('remove', this.index) | ||||
|     }, | ||||
|     validateItem () { | ||||
|       this.$v.item.$touch() | ||||
|  | ||||
|       if (this.item !== null) { | ||||
|         this.$emit('itemValidate', this.index, !this.$v.$invalid) | ||||
|       } else { | ||||
|         this.$emit('itemValidate', this.index, false) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										129
									
								
								resources/assets/js/views/invoices/ItemSelect.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								resources/assets/js/views/invoices/ItemSelect.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,129 @@ | ||||
| <template> | ||||
|   <div class="item-selector"> | ||||
|     <div v-if="item.item_id" class="selected-item"> | ||||
|       {{ item.name }} | ||||
|  | ||||
|       <span class="deselect-icon" @click="deselectItem"> | ||||
|         <font-awesome-icon icon="times-circle" /> | ||||
|       </span> | ||||
|     </div> | ||||
|     <base-select | ||||
|       v-else | ||||
|       ref="baseSelect" | ||||
|       v-model="itemSelect" | ||||
|       :options="items" | ||||
|       :show-labels="false" | ||||
|       :preserve-search="true" | ||||
|       :initial-search="item.name" | ||||
|       :invalid="invalid" | ||||
|       :placeholder="$t('invoices.item.select_an_item')" | ||||
|       label="name" | ||||
|       class="multi-select-item" | ||||
|       @value="onTextChange" | ||||
|       @select="(val) => $emit('select', val)" | ||||
|     > | ||||
|       <div slot="afterList"> | ||||
|         <button type="button" class="list-add-button" @click="openItemModal"> | ||||
|           <font-awesome-icon class="icon" icon="cart-plus" /> | ||||
|           <label>{{ $t('general.add_new_item') }}</label> | ||||
|         </button> | ||||
|       </div> | ||||
|     </base-select> | ||||
|     <div class="item-description"> | ||||
|       <base-text-area | ||||
|         v-autoresize | ||||
|         v-model="item.description" | ||||
|         :invalid-description="invalidDescription" | ||||
|         :placeholder="$t('invoices.item.type_item_description')" | ||||
|         type="text" | ||||
|         rows="1" | ||||
|         class="description-input" | ||||
|         @input="$emit('onDesriptionInput')" | ||||
|       /> | ||||
|       <div v-if="invalidDescription"> | ||||
|         <span class="text-danger">{{ $tc('validation.description_maxlength') }}</span> | ||||
|       </div> | ||||
|       <!-- <textarea type="text" v-autoresize rows="1" class="description-input" v-model="item.description" placeholder="Type Item Description (optional)" /> --> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
|  | ||||
| export default { | ||||
|   props: { | ||||
|     item: { | ||||
|       type: Object, | ||||
|       required: true | ||||
|     }, | ||||
|     invalid: { | ||||
|       type: Boolean, | ||||
|       required: false, | ||||
|       default: false | ||||
|     }, | ||||
|     invalidDescription: { | ||||
|       type: Boolean, | ||||
|       required: false, | ||||
|       default: false | ||||
|     } | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       itemSelect: null, | ||||
|       loading: false | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('item', [ | ||||
|       'items' | ||||
|     ]) | ||||
|   }, | ||||
|   watch: { | ||||
|     invalidDescription (newValue) { | ||||
|       console.log(newValue) | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('modal', [ | ||||
|       'openModal' | ||||
|     ]), | ||||
|     ...mapActions('item', [ | ||||
|       'fetchItems' | ||||
|     ]), | ||||
|     async searchItems (search) { | ||||
|       let data = { | ||||
|         filter: { | ||||
|           name: search, | ||||
|           unit: '', | ||||
|           price: '' | ||||
|         }, | ||||
|         orderByField: '', | ||||
|         orderBy: '', | ||||
|         page: 1 | ||||
|       } | ||||
|  | ||||
|       this.loading = true | ||||
|  | ||||
|       await this.fetchItems(data) | ||||
|  | ||||
|       this.loading = false | ||||
|     }, | ||||
|     onTextChange (val) { | ||||
|       this.searchItems(val) | ||||
|  | ||||
|       this.$emit('search', val) | ||||
|     }, | ||||
|     openItemModal () { | ||||
|       this.openModal({ | ||||
|         'title': 'Add Item', | ||||
|         'componentName': 'ItemModal' | ||||
|       }) | ||||
|     }, | ||||
|     deselectItem () { | ||||
|       this.itemSelect = null | ||||
|       this.$emit('deselect') | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										165
									
								
								resources/assets/js/views/invoices/Tax.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								resources/assets/js/views/invoices/Tax.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,165 @@ | ||||
| <template> | ||||
|   <div class="tax-row"> | ||||
|     <div class="d-flex align-items-center tax-select"> | ||||
|       <label class="bd-highlight pr-2 mb-0" align="right"> | ||||
|         {{ $t('general.tax') }} | ||||
|       </label> | ||||
|       <base-select | ||||
|         v-model="selectedTax" | ||||
|         :options="filteredTypes" | ||||
|         :allow-empty="false" | ||||
|         :show-labels="false" | ||||
|         :custom-label="customLabel" | ||||
|         :placeholder="$t('general.select_a_tax')" | ||||
|         track-by="name" | ||||
|         label="name" | ||||
|         @select="(val) => onSelectTax(val)" | ||||
|       > | ||||
|         <div slot="afterList"> | ||||
|           <button type="button" class="list-add-button" @click="openTaxModal"> | ||||
|             <font-awesome-icon class="icon" icon="check-circle" /> | ||||
|             <label>{{ $t('invoices.add_new_tax') }}</label> | ||||
|           </button> | ||||
|         </div> | ||||
|       </base-select> <br> | ||||
|     </div> | ||||
|     <div class="text-right tax-amount" v-html="$utils.formatMoney(taxAmount, currency)" /> | ||||
|     <div class="remove-icon-wrapper"> | ||||
|       <font-awesome-icon | ||||
|         v-if="taxes.length && index !== taxes.length - 1" | ||||
|         class="remove-icon" | ||||
|         icon="trash-alt" | ||||
|         @click="removeTax" | ||||
|       /> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
|  | ||||
| export default { | ||||
|   props: { | ||||
|     index: { | ||||
|       type: Number, | ||||
|       required: true | ||||
|     }, | ||||
|     taxData: { | ||||
|       type: Object, | ||||
|       required: true | ||||
|     }, | ||||
|     taxes: { | ||||
|       type: Array, | ||||
|       default: [] | ||||
|     }, | ||||
|     total: { | ||||
|       type: Number, | ||||
|       default: 0 | ||||
|     }, | ||||
|     totalTax: { | ||||
|       type: Number, | ||||
|       default: 0 | ||||
|     }, | ||||
|     currency: { | ||||
|       type: [Object, String], | ||||
|       required: true | ||||
|     } | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       tax: {...this.taxData}, | ||||
|       selectedTax: null | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('taxType', [ | ||||
|       'taxTypes' | ||||
|     ]), | ||||
|     filteredTypes () { | ||||
|       const clonedTypes = this.taxTypes.map(a => ({...a})) | ||||
|  | ||||
|       return clonedTypes.map((taxType) => { | ||||
|         let found = this.taxes.find(tax => tax.tax_type_id === taxType.id) | ||||
|  | ||||
|         if (found) { | ||||
|           taxType.$isDisabled = true | ||||
|         } else { | ||||
|           taxType.$isDisabled = false | ||||
|         } | ||||
|  | ||||
|         return taxType | ||||
|       }) | ||||
|     }, | ||||
|     taxAmount () { | ||||
|       if (this.tax.compound_tax && this.total) { | ||||
|         return ((this.total + this.totalTax) * this.tax.percent) / 100 | ||||
|       } | ||||
|  | ||||
|       if (this.total && this.tax.percent) { | ||||
|         return (this.total * this.tax.percent) / 100 | ||||
|       } | ||||
|  | ||||
|       return 0 | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     total: { | ||||
|       handler: 'updateTax' | ||||
|     }, | ||||
|     totalTax: { | ||||
|       handler: 'updateTax' | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     if (this.taxData.tax_type_id > 0) { | ||||
|       this.selectedTax = this.taxTypes.find(_type => _type.id === this.taxData.tax_type_id) | ||||
|     } | ||||
|  | ||||
|     this.updateTax() | ||||
|     window.hub.$on('newTax', (val) => { | ||||
|       if (!this.selectedTax) { | ||||
|         this.selectedTax = val | ||||
|         this.onSelectTax(val) | ||||
|       } | ||||
|     }) | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('modal', [ | ||||
|       'openModal' | ||||
|     ]), | ||||
|     customLabel ({ name, percent }) { | ||||
|       return `${name} - ${percent}%` | ||||
|     }, | ||||
|     onSelectTax (val) { | ||||
|       this.tax.percent = val.percent | ||||
|       this.tax.tax_type_id = val.id | ||||
|       this.tax.compound_tax = val.compound_tax | ||||
|       this.tax.name = val.name | ||||
|  | ||||
|       this.updateTax() | ||||
|     }, | ||||
|     updateTax () { | ||||
|       if (this.tax.tax_type_id === 0) { | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       this.$emit('update', { | ||||
|         'index': this.index, | ||||
|         'item': { | ||||
|           ...this.tax, | ||||
|           amount: this.taxAmount | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     removeTax () { | ||||
|       this.$emit('remove', this.index, this.tax) | ||||
|     }, | ||||
|     openTaxModal () { | ||||
|       this.openModal({ | ||||
|         'title': 'Add Tax', | ||||
|         'componentName': 'TaxTypeModal' | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										262
									
								
								resources/assets/js/views/invoices/View.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								resources/assets/js/views/invoices/View.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,262 @@ | ||||
| <template> | ||||
|   <div v-if="invoice" class="main-content invoice-view-page"> | ||||
|     <div class="page-header"> | ||||
|       <h3 class="page-title"> {{ invoice.invoice_number }}</h3> | ||||
|       <div class="page-actions row"> | ||||
|         <div class="col-xs-2 mr-3"> | ||||
|           <base-button | ||||
|             :loading="isRequestOnGoing" | ||||
|             :disabled="isRequestOnGoing" | ||||
|             :outline="true" | ||||
|             color="theme" | ||||
|             @click="onMarkAsSent" | ||||
|           > | ||||
|             {{ $t('invoices.mark_as_sent') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|         <router-link :to="`/admin/payments/${$route.params.id}/create`"> | ||||
|           <base-button | ||||
|             color="theme" | ||||
|           > | ||||
|             {{ $t('payments.record_payment') }} | ||||
|           </base-button> | ||||
|         </router-link> | ||||
|         <v-dropdown :close-on-select="false" align="left" class="filter-container"> | ||||
|           <a slot="activator" href="#"> | ||||
|             <base-button color="theme"> | ||||
|               <font-awesome-icon icon="ellipsis-h" /> | ||||
|             </base-button> | ||||
|           </a> | ||||
|           <v-dropdown-item> | ||||
|             <router-link :to="{path: `/admin/invoices/${$route.params.id}/edit`}" class="dropdown-item"> | ||||
|               <font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon"/> | ||||
|               {{ $t('general.edit') }} | ||||
|             </router-link> | ||||
|             <div class="dropdown-item" @click="removeInvoice($route.params.id)"> | ||||
|               <font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" /> | ||||
|               {{ $t('general.delete') }} | ||||
|             </div> | ||||
|           </v-dropdown-item> | ||||
|         </v-dropdown> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="invoice-sidebar"> | ||||
|       <div class="side-header"> | ||||
|         <base-input | ||||
|           v-model="searchData.searchText" | ||||
|           :placeholder="$t('general.search')" | ||||
|           input-class="inv-search" | ||||
|           icon="search" | ||||
|           type="text" | ||||
|           align-icon="right" | ||||
|           @input="onSearched()" | ||||
|         /> | ||||
|         <div | ||||
|           class="btn-group ml-3" | ||||
|           role="group" | ||||
|           aria-label="First group" | ||||
|         > | ||||
|           <v-dropdown :close-on-select="false" align="left" class="filter-container"> | ||||
|             <a slot="activator" href="#"> | ||||
|               <base-button class="inv-button inv-filter-fields-btn" color="default" size="medium"> | ||||
|                 <font-awesome-icon icon="filter" /> | ||||
|               </base-button> | ||||
|             </a> | ||||
|  | ||||
|             <div class="filter-items"> | ||||
|               <input | ||||
|                 id="filter_invoice_date" | ||||
|                 v-model="searchData.orderByField" | ||||
|                 type="radio" | ||||
|                 name="filter" | ||||
|                 class="inv-radio" | ||||
|                 value="invoice_date" | ||||
|                 @change="onSearched" | ||||
|               > | ||||
|               <label class="inv-label" for="filter_invoice_date">{{ $t('invoices.invoice_date') }}</label> | ||||
|             </div> | ||||
|             <div class="filter-items"> | ||||
|               <input | ||||
|                 id="filter_due_date" | ||||
|                 v-model="searchData.orderByField" | ||||
|                 type="radio" | ||||
|                 name="filter" | ||||
|                 class="inv-radio" | ||||
|                 value="due_date" | ||||
|                 @change="onSearched" | ||||
|               > | ||||
|               <label class="inv-label" for="filter_due_date">{{ $t('invoices.due_date') }}</label> | ||||
|             </div> | ||||
|             <div class="filter-items"> | ||||
|               <input | ||||
|                 id="filter_invoice_number" | ||||
|                 v-model="searchData.orderByField" | ||||
|                 type="radio" | ||||
|                 name="filter" | ||||
|                 class="inv-radio" | ||||
|                 value="invoice_number" | ||||
|                 @change="onSearched" | ||||
|               > | ||||
|               <label class="inv-label" for="filter_invoice_number">{{ $t('invoices.invoice_number') }}</label> | ||||
|             </div> | ||||
|           </v-dropdown> | ||||
|           <base-button class="inv-button inv-filter-sorting-btn" color="default" size="medium" @click="sortData"> | ||||
|             <font-awesome-icon v-if="getOrderBy" icon="sort-amount-up" /> | ||||
|             <font-awesome-icon v-else icon="sort-amount-down" /> | ||||
|           </base-button> | ||||
|         </div> | ||||
|       </div> | ||||
|       <base-loader v-if="isSearching" /> | ||||
|       <div v-else class="side-content"> | ||||
|         <router-link | ||||
|           v-for="(invoice,index) in invoices" | ||||
|           :to="`/admin/invoices/${invoice.id}/view`" | ||||
|           :key="index" | ||||
|           class="side-invoice" | ||||
|         > | ||||
|           <div class="left"> | ||||
|             <div class="inv-name">{{ invoice.user.name }}</div> | ||||
|             <div class="inv-number">{{ invoice.invoice_number }}</div> | ||||
|             <div :class="'inv-status-'+invoice.status.toLowerCase()" class="inv-status">{{ invoice.status }}</div> | ||||
|           </div> | ||||
|           <div class="right"> | ||||
|             <div class="inv-amount" v-html="$utils.formatMoney(invoice.due_amount, invoice.user.currency)" /> | ||||
|             <div class="inv-date">{{ invoice.formattedInvoiceDate }}</div> | ||||
|           </div> | ||||
|         </router-link> | ||||
|         <p v-if="!invoices.length" class="no-result"> | ||||
|           {{ $t('invoices.no_matching_invoices') }} | ||||
|         </p> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="invoice-view-page-container" > | ||||
|       <iframe :src="`${shareableLink}`" class="frame-style"/> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import { mapActions } from 'vuex' | ||||
| const _ = require('lodash') | ||||
| export default { | ||||
|   data () { | ||||
|     return { | ||||
|       id: null, | ||||
|       count: null, | ||||
|       invoices: [], | ||||
|       invoice: null, | ||||
|       currency: null, | ||||
|       shareableLink: null, | ||||
|       searchData: { | ||||
|         orderBy: null, | ||||
|         orderByField: null, | ||||
|         searchText: null | ||||
|       }, | ||||
|       isRequestOnGoing: false, | ||||
|       isSearching: false | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     getOrderBy () { | ||||
|       if (this.searchData.orderBy === 'asc' || this.searchData.orderBy == null) { | ||||
|         return true | ||||
|       } | ||||
|       return false | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     '$route.params.id' (val) { | ||||
|       this.fetchInvoice() | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     this.loadInvoices() | ||||
|     this.onSearched = _.debounce(this.onSearched, 500) | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('invoice', [ | ||||
|       'fetchInvoices', | ||||
|       'fetchViewInvoice', | ||||
|       'getRecord', | ||||
|       'searchInvoice', | ||||
|       'markAsSent', | ||||
|       'deleteInvoice', | ||||
|       'selectInvoice' | ||||
|     ]), | ||||
|     async loadInvoices () { | ||||
|       let response = await this.fetchInvoices() | ||||
|       if (response.data) { | ||||
|         this.invoices = response.data.invoices.data | ||||
|       } | ||||
|       this.fetchInvoice() | ||||
|     }, | ||||
|     async onSearched () { | ||||
|       let data = '' | ||||
|       if (this.searchData.searchText !== '' && this.searchData.searchText !== null && this.searchData.searchText !== undefined) { | ||||
|         data += `search=${this.searchData.searchText}&` | ||||
|       } | ||||
|  | ||||
|       if (this.searchData.orderBy !== null && this.searchData.orderBy !== undefined) { | ||||
|         data += `orderBy=${this.searchData.orderBy}&` | ||||
|       } | ||||
|  | ||||
|       if (this.searchData.orderByField !== null && this.searchData.orderByField !== undefined) { | ||||
|         data += `orderByField=${this.searchData.orderByField}` | ||||
|       } | ||||
|       this.isSearching = true | ||||
|       let response = await this.searchInvoice(data) | ||||
|       this.isSearching = false | ||||
|       if (response.data) { | ||||
|         this.invoices = response.data.invoices.data | ||||
|       } | ||||
|     }, | ||||
|     async fetchInvoice () { | ||||
|       let invoice = await this.fetchViewInvoice(this.$route.params.id) | ||||
|  | ||||
|       if (invoice.data) { | ||||
|         this.invoice = invoice.data.invoice | ||||
|         this.shareableLink = invoice.data.shareable_link | ||||
|         this.currency = invoice.data.invoice.user.currency | ||||
|       } | ||||
|     }, | ||||
|     sortData () { | ||||
|       if (this.searchData.orderBy === 'asc') { | ||||
|         this.searchData.orderBy = 'desc' | ||||
|         this.onSearched() | ||||
|         return true | ||||
|       } | ||||
|       this.searchData.orderBy = 'asc' | ||||
|       this.onSearched() | ||||
|       return true | ||||
|     }, | ||||
|     async onMarkAsSent () { | ||||
|       this.isRequestOnGoing = true | ||||
|       let response = await this.markAsSent({id: this.invoice.id}) | ||||
|       this.isRequestOnGoing = false | ||||
|       if (response.data) { | ||||
|         window.toastr['success'](this.$tc('invoices.marked_as_sent_message')) | ||||
|       } | ||||
|     }, | ||||
|     async removeInvoice (id) { | ||||
|       this.selectInvoice([parseInt(id)]) | ||||
|       this.id = id | ||||
|       swal({ | ||||
|         title: 'Deleted', | ||||
|         text: 'you will not be able to recover this invoice!', | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let request = await this.deleteInvoice(this.id) | ||||
|           if (request.data.success) { | ||||
|             window.toastr['success'](this.$tc('invoices.deleted_message', 1)) | ||||
|             this.$router.push('/admin/invoices/') | ||||
|           } else if (request.data.error) { | ||||
|             window.toastr['error'](request.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										217
									
								
								resources/assets/js/views/items/Create.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								resources/assets/js/views/items/Create.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,217 @@ | ||||
| <template> | ||||
|   <div class="main-content item-create"> | ||||
|     <div class="page-header"> | ||||
|       <h3 class="page-title">{{ isEdit ? $t('items.edit_item') : $t('items.new_item') }}</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/items">{{ $tc('items.item',2) }}</router-link></li> | ||||
|         <li class="breadcrumb-item"><a href="#"> {{ isEdit ? $t('items.edit_item') : $t('items.new_item') }}</a></li> | ||||
|       </ol> | ||||
|     </div> | ||||
|     <div class="row"> | ||||
|       <div class="col-sm-6"> | ||||
|         <div class="card"> | ||||
|           <form action="" @submit.prevent="submitItem"> | ||||
|             <div class="card-body"> | ||||
|               <div class="form-group"> | ||||
|                 <label class="control-label">{{ $t('items.name') }}</label><span class="text-danger"> *</span> | ||||
|                 <base-input | ||||
|                   v-model.trim="formData.name" | ||||
|                   :invalid="$v.formData.name.$error" | ||||
|                   focus | ||||
|                   type="text" | ||||
|                   name="name" | ||||
|                   @input="$v.formData.name.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.formData.name.$error"> | ||||
|                   <span v-if="!$v.formData.name.required" class="text-danger">{{ $t('validation.required') }} </span> | ||||
|                   <span v-if="!$v.formData.name.minLength" class="text-danger"> | ||||
|                     {{ $tc('validation.name_min_length', $v.formData.name.$params.minLength.min, { count: $v.formData.name.$params.minLength.min }) }} | ||||
|                   </span> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label>{{ $t('items.price') }}</label><span class="text-danger"> *</span> | ||||
|                 <div class="base-input"> | ||||
|                   <money | ||||
|                     :invalid="$v.formData.price.$error" | ||||
|                     v-model="price" | ||||
|                     v-bind="defaultCurrencyForInput" | ||||
|                     class="input-field" | ||||
|                     @input="$v.formData.price.$touch()" | ||||
|                   /> | ||||
|                 </div> | ||||
|                 <div v-if="$v.formData.price.$error"> | ||||
|                   <span v-if="!$v.formData.price.required" class="text-danger">{{ $t('validation.required') }} </span> | ||||
|                   <span v-if="!$v.formData.price.maxLength" class="text-danger">{{ $t('validation.price_maxlength') }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label>{{ $t('items.unit') }}</label> | ||||
|                 <base-select | ||||
|                   v-model="formData.unit" | ||||
|                   :options="units" | ||||
|                   :searchable="true" | ||||
|                   :show-labels="false" | ||||
|                   :placeholder="$t('items.select_a_unit')" | ||||
|                   label="name" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label for="description">{{ $t('items.description') }}</label> | ||||
|                 <base-text-area | ||||
|                   v-model="formData.description" | ||||
|                   rows="2" | ||||
|                   name="description" | ||||
|                   @input="$v.formData.description.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.formData.description.$error"> | ||||
|                   <span v-if="!$v.formData.description.maxLength" class="text-danger">{{ $t('validation.description_maxlength') }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <base-button | ||||
|                   :loading="isLoading" | ||||
|                   :disabled="isLoading" | ||||
|                   icon="save" | ||||
|                   color="theme" | ||||
|                   type="submit" | ||||
|                   class="collapse-button" | ||||
|                 > | ||||
|                   {{ isEdit ? $t('items.update_item') : $t('items.save_item') }} | ||||
|                 </base-button> | ||||
|               </div> | ||||
|             </div> | ||||
|           </form> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
| const { required, minLength, numeric, alpha, minValue, maxLength} = require('vuelidate/lib/validators') | ||||
| export default { | ||||
|   mixins: { | ||||
|     validationMixin | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       isLoading: false, | ||||
|       title: 'Add Item', | ||||
|       units: [ | ||||
|         { name: 'box', value: 'box' }, | ||||
|         { name: 'cm', value: 'cm' }, | ||||
|         { name: 'dz', value: 'dz' }, | ||||
|         { name: 'ft', value: 'ft' }, | ||||
|         { name: 'g', value: 'g' }, | ||||
|         { name: 'in', value: 'in' }, | ||||
|         { name: 'kg', value: 'kg' }, | ||||
|         { name: 'km', value: 'km' }, | ||||
|         { name: 'lb', value: 'lb' }, | ||||
|         { name: 'mg', value: 'mg' } | ||||
|       ], | ||||
|       formData: { | ||||
|         name: '', | ||||
|         description: '', | ||||
|         price: '', | ||||
|         unit: null | ||||
|       }, | ||||
|       money: { | ||||
|         decimal: '.', | ||||
|         thousands: ',', | ||||
|         prefix: '$ ', | ||||
|         precision: 2, | ||||
|         masked: false | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('currency', [ | ||||
|       'defaultCurrencyForInput' | ||||
|     ]), | ||||
|     price: { | ||||
|       get: function () { | ||||
|         return this.formData.price / 100 | ||||
|       }, | ||||
|       set: function (newValue) { | ||||
|         this.formData.price = newValue * 100 | ||||
|       } | ||||
|     }, | ||||
|     isEdit () { | ||||
|       if (this.$route.name === 'items.edit') { | ||||
|         return true | ||||
|       } | ||||
|       return false | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     if (this.isEdit) { | ||||
|       this.loadEditData() | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     formData: { | ||||
|       name: { | ||||
|         required, | ||||
|         minLength: minLength(3) | ||||
|       }, | ||||
|       price: { | ||||
|         required, | ||||
|         numeric, | ||||
|         maxLength: maxLength(10), | ||||
|         minValue: minValue(0.1) | ||||
|       }, | ||||
|       description: { | ||||
|         maxLength: maxLength(255) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('item', [ | ||||
|       'addItem', | ||||
|       'fetchItem', | ||||
|       'updateItem' | ||||
|     ]), | ||||
|     async loadEditData () { | ||||
|       let response = await this.fetchItem(this.$route.params.id) | ||||
|       this.formData = response.data.item | ||||
|       this.formData.unit = this.units.find(_unit => response.data.item.unit === _unit.name) | ||||
|       this.fractional_price = response.data.item.price | ||||
|     }, | ||||
|     async submitItem () { | ||||
|       this.$v.formData.$touch() | ||||
|       if (this.$v.$invalid) { | ||||
|         return false | ||||
|       } | ||||
|       if (this.formData.unit) { | ||||
|         this.formData.unit = this.formData.unit.name | ||||
|       } | ||||
|       if (this.isEdit) { | ||||
|         this.isLoading = true | ||||
|         let response = await this.updateItem(this.formData) | ||||
|         if (response.data) { | ||||
|           this.isLoading = false | ||||
|           window.toastr['success'](this.$tc('items.updated_message')) | ||||
|           this.$router.push('/admin/items') | ||||
|           return true | ||||
|         } | ||||
|         window.toastr['error'](response.data.error) | ||||
|       } else { | ||||
|         this.isLoading = true | ||||
|         let response = await this.addItem(this.formData) | ||||
|  | ||||
|         if (response.data) { | ||||
|           window.toastr['success'](this.$tc('items.created_message')) | ||||
|           this.$router.push('/admin/items') | ||||
|           this.isLoading = false | ||||
|           return true | ||||
|         } | ||||
|         window.toastr['success'](response.data.success) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										408
									
								
								resources/assets/js/views/items/Index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										408
									
								
								resources/assets/js/views/items/Index.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,408 @@ | ||||
| <template> | ||||
|   <div class="items main-content"> | ||||
|     <div class="page-header"> | ||||
|       <div class="d-flex flex-row"> | ||||
|         <div> | ||||
|           <h3 class="page-title">{{ $tc('items.item', 2) }}</h3> | ||||
|         </div> | ||||
|       </div> | ||||
|       <ol class="breadcrumb"> | ||||
|         <li class="breadcrumb-item"> | ||||
|           <router-link | ||||
|             slot="item-title" | ||||
|             to="dashboard"> | ||||
|             {{ $t('general.home') }} | ||||
|           </router-link> | ||||
|         </li> | ||||
|         <li class="breadcrumb-item"> | ||||
|           <router-link | ||||
|             slot="item-title" | ||||
|             to="#"> | ||||
|             {{ $tc('items.item', 2) }} | ||||
|           </router-link> | ||||
|         </li> | ||||
|       </ol> | ||||
|       <div class="page-actions row"> | ||||
|         <div class="col-xs-2 mr-4"> | ||||
|           <base-button | ||||
|             v-show="totalItems || filtersApplied" | ||||
|             :outline="true" | ||||
|             :icon="filterIcon" | ||||
|             color="theme" | ||||
|             size="large" | ||||
|             right-icon | ||||
|             @click="toggleFilter" | ||||
|           > | ||||
|             {{ $t('general.filter') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|         <router-link slot="item-title" class="col-xs-2" to="items/create"> | ||||
|           <base-button | ||||
|             color="theme" | ||||
|             icon="plus" | ||||
|             size="large" | ||||
|           > | ||||
|             {{ $t('items.add_item') }} | ||||
|           </base-button> | ||||
|         </router-link> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <transition name="fade"> | ||||
|       <div v-show="showFilters" class="filter-section"> | ||||
|         <div class="row"> | ||||
|           <div class="col-sm-4"> | ||||
|             <label class="form-label"> {{ $tc('items.name') }} </label> | ||||
|             <base-input | ||||
|               v-model="filters.name" | ||||
|               type="text" | ||||
|               name="name" | ||||
|               autocomplete="off" | ||||
|             /> | ||||
|           </div> | ||||
|           <div class="col-sm-4"> | ||||
|             <label class="form-label"> {{ $tc('items.unit') }} </label> | ||||
|             <base-select | ||||
|               v-model="filters.unit" | ||||
|               :options="units" | ||||
|               :searchable="true" | ||||
|               :show-labels="false" | ||||
|               :placeholder="$t('items.select_a_unit')" | ||||
|               label="name" | ||||
|               autocomplete="off" | ||||
|             /> | ||||
|           </div> | ||||
|           <div class="col-sm-4"> | ||||
|             <label class="form-label"> {{ $tc('items.price') }} </label> | ||||
|             <base-input | ||||
|               v-model="filters.price" | ||||
|               type="text" | ||||
|               name="name" | ||||
|               autocomplete="off" | ||||
|             /> | ||||
|           </div> | ||||
|           <label class="clear-filter" @click="clearFilter"> {{ $t('general.clear_all') }}</label> | ||||
|         </div> | ||||
|       </div> | ||||
|     </transition> | ||||
|  | ||||
|     <div v-cloak v-show="showEmptyScreen" class="col-xs-1 no-data-info" align="center"> | ||||
|       <satellite-icon class="mt-5 mb-4"/> | ||||
|       <div class="row" align="center"> | ||||
|         <label class="col title">{{ $t('items.no_items') }}</label> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <label class="description col mt-1" align="center">{{ $t('items.list_of_items') }}</label> | ||||
|       </div> | ||||
|       <div class="btn-container"> | ||||
|         <base-button | ||||
|           :outline="true" | ||||
|           color="theme" | ||||
|           class="mt-3" | ||||
|           size="large" | ||||
|           @click="$router.push('items/create')" | ||||
|         > | ||||
|           {{ $t('items.add_new_item') }} | ||||
|         </base-button> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <div v-show="!showEmptyScreen" class="table-container"> | ||||
|       <div class="table-actions mt-5"> | ||||
|         <p class="table-stats">{{ $t('general.showing') }}: <b>{{ items.length }}</b> {{ $t('general.of') }} <b>{{ totalItems }}</b></p> | ||||
|         <transition name="fade"> | ||||
|           <v-dropdown v-if="selectedItems.length" :show-arrow="false"> | ||||
|             <span slot="activator" href="#" class="table-actions-button dropdown-toggle"> | ||||
|               {{ $t('general.actions') }} | ||||
|             </span> | ||||
|             <v-dropdown-item> | ||||
|               <div class="dropdown-item" @click="removeMultipleItems"> | ||||
|                 <font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" /> | ||||
|                 {{ $t('general.delete') }} | ||||
|               </div> | ||||
|             </v-dropdown-item> | ||||
|           </v-dropdown> | ||||
|         </transition> | ||||
|       </div> | ||||
|  | ||||
|       <div class="custom-control custom-checkbox"> | ||||
|         <input | ||||
|           id="select-all" | ||||
|           v-model="selectAllFieldStatus" | ||||
|           type="checkbox" | ||||
|           class="custom-control-input" | ||||
|           @change="selectAllItems" | ||||
|         > | ||||
|         <label v-show="!isRequestOngoing" for="select-all" class="custom-control-label selectall"> | ||||
|           <span class="select-all-label">{{ $t('general.select_all') }} </span> | ||||
|         </label> | ||||
|       </div> | ||||
|  | ||||
|       <table-component | ||||
|         ref="table" | ||||
|         :data="fetchData" | ||||
|         :show-filter="false" | ||||
|         table-class="table" | ||||
|       > | ||||
|  | ||||
|         <table-column | ||||
|           :sortable="false" | ||||
|           :filterable="false" | ||||
|           cell-class="no-click" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <div class="custom-control custom-checkbox"> | ||||
|               <input | ||||
|                 :id="row.id" | ||||
|                 v-model="selectField" | ||||
|                 :value="row.id" | ||||
|                 type="checkbox" | ||||
|                 class="custom-control-input" | ||||
|               > | ||||
|               <label :for="row.id" class="custom-control-label"/> | ||||
|             </div> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <table-column | ||||
|           :label="$t('items.name')" | ||||
|           show="name" | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('items.unit')" | ||||
|           show="unit" | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('items.price')" | ||||
|           show="price" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <span> {{ $t('items.price') }} </span> | ||||
|             <div v-html="$utils.formatMoney(row.price, defaultCurrency)" /> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <table-column | ||||
|           :label="$t('items.added_on')" | ||||
|           sort-as="created_at" | ||||
|           show="formattedCreatedAt" | ||||
|         /> | ||||
|         <table-column | ||||
|           :sortable="false" | ||||
|           :filterable="false" | ||||
|           cell-class="action-dropdown" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <span> {{ $t('items.action') }} </span> | ||||
|             <v-dropdown> | ||||
|               <a slot="activator" href="#"> | ||||
|                 <dot-icon /> | ||||
|               </a> | ||||
|               <v-dropdown-item> | ||||
|  | ||||
|                 <router-link :to="{path: `items/${row.id}/edit`}" class="dropdown-item"> | ||||
|                   <font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon" /> | ||||
|                   {{ $t('general.edit') }} | ||||
|                 </router-link> | ||||
|  | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item> | ||||
|                 <div class="dropdown-item" @click="removeItems(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' | ||||
| import DotIcon from '../../components/icon/DotIcon' | ||||
| import SatelliteIcon from '../../components/icon/SatelliteIcon' | ||||
| import BaseButton from '../../../js/components/base/BaseButton' | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     DotIcon, | ||||
|     SatelliteIcon, | ||||
|     BaseButton | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       id: null, | ||||
|       showFilters: false, | ||||
|       sortedBy: 'created_at', | ||||
|       units: [ | ||||
|         { name: 'box', value: 'box' }, | ||||
|         { name: 'cm', value: 'cm' }, | ||||
|         { name: 'dz', value: 'dz' }, | ||||
|         { name: 'ft', value: 'ft' }, | ||||
|         { name: 'g', value: 'g' }, | ||||
|         { name: 'in', value: 'in' }, | ||||
|         { name: 'kg', value: 'kg' }, | ||||
|         { name: 'km', value: 'km' }, | ||||
|         { name: 'lb', value: 'lb' }, | ||||
|         { name: 'mg', value: 'mg' } | ||||
|       ], | ||||
|       isRequestOngoing: true, | ||||
|       filtersApplied: false, | ||||
|       filters: { | ||||
|         name: '', | ||||
|         unit: '', | ||||
|         price: '' | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('item', [ | ||||
|       'items', | ||||
|       'selectedItems', | ||||
|       'totalItems', | ||||
|       'selectAllField' | ||||
|     ]), | ||||
|     ...mapGetters('currency', [ | ||||
|       'defaultCurrency' | ||||
|     ]), | ||||
|     showEmptyScreen () { | ||||
|       return !this.totalItems && !this.isRequestOngoing && !this.filtersApplied | ||||
|     }, | ||||
|     filterIcon () { | ||||
|       return (this.showFilters) ? 'times' : 'filter' | ||||
|     }, | ||||
|     selectField: { | ||||
|       get: function () { | ||||
|         return this.selectedItems | ||||
|       }, | ||||
|       set: function (val) { | ||||
|         this.selectItem(val) | ||||
|       } | ||||
|     }, | ||||
|     selectAllFieldStatus: { | ||||
|       get: function () { | ||||
|         return this.selectAllField | ||||
|       }, | ||||
|       set: function (val) { | ||||
|         this.setSelectAllState(val) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     filters: { | ||||
|       handler: 'setFilters', | ||||
|       deep: true | ||||
|     } | ||||
|   }, | ||||
|   destroyed () { | ||||
|     if (this.selectAllField) { | ||||
|       this.selectAllItems() | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('item', [ | ||||
|       'fetchItems', | ||||
|       'selectAllItems', | ||||
|       'selectItem', | ||||
|       'deleteItem', | ||||
|       'deleteMultipleItems', | ||||
|       'setSelectAllState' | ||||
|     ]), | ||||
|     refreshTable () { | ||||
|       this.$refs.table.refresh() | ||||
|     }, | ||||
|     async fetchData ({ page, filter, sort }) { | ||||
|       let data = { | ||||
|         search: this.filters.name !== null ? this.filters.name : '', | ||||
|         unit: this.filters.unit !== null ? this.filters.unit.name : '', | ||||
|         price: this.filters.price * 100, | ||||
|         orderByField: sort.fieldName || 'created_at', | ||||
|         orderBy: sort.order || 'desc', | ||||
|         page | ||||
|       } | ||||
|  | ||||
|       this.isRequestOngoing = true | ||||
|       let response = await this.fetchItems(data) | ||||
|       this.isRequestOngoing = false | ||||
|  | ||||
|       return { | ||||
|         data: response.data.items.data, | ||||
|         pagination: { | ||||
|           totalPages: response.data.items.last_page, | ||||
|           currentPage: page | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     setFilters () { | ||||
|       this.filtersApplied = true | ||||
|       this.refreshTable() | ||||
|     }, | ||||
|     clearFilter () { | ||||
|       this.filters = { | ||||
|         name: '', | ||||
|         unit: '', | ||||
|         price: '' | ||||
|       } | ||||
|  | ||||
|       this.$nextTick(() => { | ||||
|         this.filtersApplied = false | ||||
|       }) | ||||
|     }, | ||||
|     toggleFilter () { | ||||
|       if (this.showFilters && this.filtersApplied) { | ||||
|         this.clearFilter() | ||||
|         this.refreshTable() | ||||
|       } | ||||
|  | ||||
|       this.showFilters = !this.showFilters | ||||
|     }, | ||||
|     async removeItems (id) { | ||||
|       this.id = id | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$tc('items.confirm_delete'), | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let res = await this.deleteItem(this.id) | ||||
|           if (res.data.success) { | ||||
|             window.toastr['success'](this.$tc('items.deleted_message', 1)) | ||||
|             this.$refs.table.refresh() | ||||
|             return true | ||||
|           } | ||||
|  | ||||
|           if (res.data.error === 'item_attached') { | ||||
|             window.toastr['error'](this.$tc('items.item_attached_message'), this.$t('general.action_failed')) | ||||
|             return true | ||||
|           } | ||||
|  | ||||
|           window.toastr['error'](res.data.message) | ||||
|           return true | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     async removeMultipleItems () { | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$tc('items.confirm_delete', 2), | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let res = await this.deleteMultipleItems() | ||||
|           if (res.data.success) { | ||||
|             window.toastr['success'](this.$tc('items.deleted_message', 2)) | ||||
|             this.$refs.table.refresh() | ||||
|           } else if (res.data.error) { | ||||
|             window.toastr['error'](res.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										83
									
								
								resources/assets/js/views/layouts/LayoutBasic.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								resources/assets/js/views/layouts/LayoutBasic.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | ||||
| <template> | ||||
|   <div class="template-container" v-if="isAppLoaded"> | ||||
|     <base-modal /> | ||||
|     <site-header/> | ||||
|     <site-sidebar type="basic"/> | ||||
|  | ||||
|     <transition | ||||
|       name="fade" | ||||
|       mode="out-in"> | ||||
|       <router-view /> | ||||
|     </transition> | ||||
|     <site-footer/> | ||||
|   </div> | ||||
|   <div v-else class="template-container"> | ||||
|     <font-awesome-icon icon="spinner" class="fa-spin"/> | ||||
|   </div> | ||||
| </template> | ||||
| <script type="text/babel"> | ||||
| import SiteHeader from './partials/TheSiteHeader.vue' | ||||
| import SiteFooter from './partials/TheSiteFooter.vue' | ||||
| import SiteSidebar from './partials/TheSiteSidebar.vue' | ||||
| import Layout from '../../helpers/layout' | ||||
| import BaseModal from '../../components/base/modal/BaseModal' | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     SiteHeader, SiteSidebar, SiteFooter, BaseModal | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       'header': 'header' | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters([ | ||||
|       'isAppLoaded' | ||||
|     ]), | ||||
|  | ||||
|     ...mapGetters('company', { | ||||
|       selectedCompany: 'getSelectedCompany', | ||||
|       companies: 'getCompanies' | ||||
|     }), | ||||
|  | ||||
|     isShow () { | ||||
|       return true | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     Layout.set('layout-default') | ||||
|   }, | ||||
|  | ||||
|   created() { | ||||
|     this.bootstrap().then((res) => { | ||||
|       this.setInitialCompany() | ||||
|     }) | ||||
|   }, | ||||
|  | ||||
|   methods: { | ||||
|     ...mapActions(['bootstrap']), | ||||
|     ...mapActions('company', ['setSelectedCompany']), | ||||
|     setInitialCompany() { | ||||
|       let selectedCompany = Ls.get('selectedCompany') !== null | ||||
|  | ||||
|       if (selectedCompany) { | ||||
|         let foundCompany = this.companies.find((company) => company.id === parseInt(selectedCompany)) | ||||
|  | ||||
|         if (foundCompany) { | ||||
|           this.setSelectedCompany(foundCompany) | ||||
|           return | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       this.setSelectedCompany(this.companies[0]) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| <style lang="scss" scoped> | ||||
| body { | ||||
|   background-color: #f8f8f8; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										55
									
								
								resources/assets/js/views/layouts/LayoutLogin.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								resources/assets/js/views/layouts/LayoutLogin.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | ||||
| <template> | ||||
|   <div class="login-page login-3"> | ||||
|     <div class="site-wrapper"> | ||||
|       <div class="login-box"> | ||||
|         <div class="box-wrapper"> | ||||
|           <div class="logo-main"> | ||||
|             <a href="/admin"> | ||||
|               <img | ||||
|                 src="/assets/img/crater-logo.png" | ||||
|                 alt="Crater Logo"> | ||||
|             </a> | ||||
|           </div> | ||||
|           <router-view></router-view> | ||||
|           <div class="page-copyright"> | ||||
|             <p>{{ $t('layout_login.copyright_crater') }}</p> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="content-box"> | ||||
|         <h1>{{ $t('layout_login.super_simple_invoicing') }}<br> | ||||
|           {{ $t('layout_login.for_freelancer') }}<br> | ||||
|           {{ $t('layout_login.small_businesses') }} <br> | ||||
|         </h1> | ||||
|  | ||||
|         <p> | ||||
|           {{ $t('layout_login.crater_help') }}<br> | ||||
|           {{ $t('layout_login.invoices_and_estimates') }}<br> | ||||
|         </p> | ||||
|  | ||||
|         <div class="content-bottom"/> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script type="text/babel"> | ||||
| export default { | ||||
|   watch: { | ||||
|     $route: 'onRouteChange' | ||||
|   }, | ||||
|   mounted () { | ||||
|     this.setLayoutClasses() | ||||
|   }, | ||||
|   methods: { | ||||
|     setLayoutClasses () { | ||||
|       let body = $('body') | ||||
|       body.removeClass() | ||||
|       body.addClass('login-page login-1 skin-crater') | ||||
|     } | ||||
|   }, | ||||
|   onRouteChange () { | ||||
|     $('body').removeClass('login-page') | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										27
									
								
								resources/assets/js/views/layouts/LayoutWizard.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								resources/assets/js/views/layouts/LayoutWizard.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| <template> | ||||
|   <div class="site-wrapper"> | ||||
|     <div class="container"> | ||||
|       <router-view></router-view> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script type="text/babel"> | ||||
| export default { | ||||
|   watch: { | ||||
|     $route: 'onRouteChange' | ||||
|   }, | ||||
|   mounted () { | ||||
|     this.setLayoutBackground() | ||||
|   }, | ||||
|  | ||||
|   destroyed () { | ||||
|     document.body.style.backgroundColor = '#EBF1FA' | ||||
|   }, | ||||
|   methods: { | ||||
|     setLayoutBackground () { | ||||
|       document.body.style.backgroundColor = '#f9fbff' | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										21
									
								
								resources/assets/js/views/layouts/partials/TheSiteFooter.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								resources/assets/js/views/layouts/partials/TheSiteFooter.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| <template> | ||||
|   <footer class="site-footer"> | ||||
|     <div class="text-right"> | ||||
|       {{ $t('general.powered_by') }} | ||||
|       <a | ||||
|         href="http://bytefury.com/" | ||||
|         target="_blank">{{ $t('general.bytefury') }} | ||||
|       </a> | ||||
|     </div> | ||||
|   </footer> | ||||
| </template> | ||||
|  | ||||
| <script type="text/babel"> | ||||
| export default { | ||||
|   data () { | ||||
|     return { | ||||
|       footer: 'footer' | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										98
									
								
								resources/assets/js/views/layouts/partials/TheSiteHeader.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								resources/assets/js/views/layouts/partials/TheSiteHeader.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,98 @@ | ||||
| <template> | ||||
|   <header class="site-header"> | ||||
|     <a href="/" class="brand-main"> | ||||
|       <img | ||||
|         id="logo-white" | ||||
|         src="/assets/img/logo-white.png" | ||||
|         alt="Crater Logo" | ||||
|         class="d-none d-md-inline" | ||||
|       > | ||||
|       <img | ||||
|         id="logo-mobile" | ||||
|         src="/assets/img/crater-white-small.png" | ||||
|         alt="Laraspace Logo" | ||||
|         class="d-md-none"> | ||||
|     </a> | ||||
|  | ||||
|     <a | ||||
|       href="#" | ||||
|       class="nav-toggle" | ||||
|       @click="onNavToggle" | ||||
|     > | ||||
|       <div class="hamburger hamburger--arrowturn"> | ||||
|         <div class="hamburger-box"> | ||||
|           <div class="hamburger-inner"/> | ||||
|         </div> | ||||
|       </div> | ||||
|     </a> | ||||
|     <ul class="action-list"> | ||||
|       <li> | ||||
|         <v-dropdown :show-arrow="false"> | ||||
|           <a slot="activator" href="#"> | ||||
|             <font-awesome-icon icon="plus" /> | ||||
|           </a> | ||||
|           <v-dropdown-item> | ||||
|             <router-link class="dropdown-item" to="/admin/invoices/create"> | ||||
|               <font-awesome-icon icon="file-alt" class="dropdown-item-icon" /> <span> {{ $t('invoices.new_invoice') }} </span> | ||||
|             </router-link> | ||||
|           </v-dropdown-item> | ||||
|           <v-dropdown-item> | ||||
|             <router-link class="dropdown-item" to="/admin/estimates/create"> | ||||
|               <font-awesome-icon class="dropdown-item-icon" icon="file" /> <span> {{ $t('estimates.new_estimate') }} </span> | ||||
|             </router-link> | ||||
|           </v-dropdown-item> | ||||
|           <v-dropdown-item> | ||||
|             <router-link class="dropdown-item" to="/admin/customers/create"> | ||||
|               <font-awesome-icon class="dropdown-item-icon" icon="user" />  <span> {{ $t('customers.new_customer') }} </span> | ||||
|             </router-link> | ||||
|           </v-dropdown-item> | ||||
|         </v-dropdown> | ||||
|       </li> | ||||
|       <li> | ||||
|         <v-dropdown :show-arrow="false"> | ||||
|           <a | ||||
|             slot="activator" | ||||
|             href="#" | ||||
|             data-toggle="dropdown" | ||||
|             aria-haspopup="true" | ||||
|             aria-expanded="false" | ||||
|             class="avatar" | ||||
|           > | ||||
|             <img src="/images/avatar.png" alt="Avatar"> | ||||
|           </a> | ||||
|           <v-dropdown-item> | ||||
|             <router-link class="dropdown-item" to="/admin/settings"> | ||||
|               <font-awesome-icon icon="cogs" class="dropdown-item-icon"/> <span> {{ $t('navigation.settings') }} </span> | ||||
|             </router-link> | ||||
|           </v-dropdown-item> | ||||
|           <v-dropdown-item> | ||||
|             <a | ||||
|               href="#" | ||||
|               class="dropdown-item" | ||||
|               @click.prevent="logout" | ||||
|             > | ||||
|               <font-awesome-icon icon="sign-out-alt" class="dropdown-item-icon"/> <span> {{ $t('navigation.logout') }} </span> | ||||
|             </a> | ||||
|           </v-dropdown-item> | ||||
|         </v-dropdown> | ||||
|       </li> | ||||
|     </ul> | ||||
|   </header> | ||||
| </template> | ||||
| <script type="text/babel"> | ||||
| import { mapGetters, mapActions } from 'vuex' | ||||
|  | ||||
| export default { | ||||
|   methods: { | ||||
|     ...mapActions({ | ||||
|       companySelect: 'changeCompany' | ||||
|     }), | ||||
|     ...mapActions('auth', [ | ||||
|       'logout' | ||||
|     ]), | ||||
|     onNavToggle () { | ||||
|       this.$utils.toggleSidebar() | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| @ -0,0 +1,459 @@ | ||||
| <template> | ||||
|   <div class="header-bottom"> | ||||
|     <div class="header-nav vue-dropdown-menu"> | ||||
|       <v-dropdown active-url="/admin/dashboard"> | ||||
|         <template slot="title"> | ||||
|           <a href="#"> | ||||
|             <i class="icon-fa icon-fa-dashboard"/>{{ $t('navigation.dashboard') }} | ||||
|             <span class="icon-fa arrow icon-fa-fw"/> | ||||
|           </a> | ||||
|         </template> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/dashboard/basic"> | ||||
|               Basic | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/dashboard/ecommerce"> | ||||
|               Ecommerce | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/dashboard/finance"> | ||||
|               Finance | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|       </v-dropdown> | ||||
|       <v-dropdown active-url="/admin/layouts"> | ||||
|         <template slot="title"> | ||||
|           <a href="#"> | ||||
|             <i class="icon-fa icon-fa-th-large"/>Layouts | ||||
|             <span class="icon-fa arrow icon-fa-fw"/> | ||||
|           </a> | ||||
|         </template> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/layouts/sidebar"> | ||||
|               Sidebar | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/layouts/horizontal"> | ||||
|               Horizontal | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/layouts/icons-sidebar"> | ||||
|               Icon Sidebar | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|       </v-dropdown> | ||||
|       <v-dropdown active-url="/admin/basic-ui"> | ||||
|         <template slot="title"> | ||||
|           <a href="#"> | ||||
|             <i class="icon-fa icon-fa-star"/>Basic UI | ||||
|             <span class="icon-fa arrow icon-fa-fw"/> | ||||
|           </a> | ||||
|         </template> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/basic-ui/buttons"> | ||||
|               Buttons | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/basic-ui/cards"> | ||||
|               Cards | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/basic-ui/tabs"> | ||||
|               Tabs & Accordians | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/basic-ui/typography"> | ||||
|               Typography | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/basic-ui/tables"> | ||||
|               Tables | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/basic-ui/modals"> | ||||
|               Modals | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/basic-ui/progress-bars"> | ||||
|               Progress Bar | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|       </v-dropdown> | ||||
|       <v-dropdown active-url="/admin/components"> | ||||
|         <template slot="title"> | ||||
|           <a href="#"> | ||||
|             <i class="icon-fa icon-fa-puzzle-piece"/>Components | ||||
|             <span class="icon-fa arrow icon-fa-fw"/> | ||||
|           </a> | ||||
|         </template> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/components/calendar"> | ||||
|               Calendar | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/components/datatables"> | ||||
|               Jquery Datatables | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/components/mail-box"> | ||||
|               MailBox | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/components/calendar"> | ||||
|               Calendar | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/components/datatables"> | ||||
|               Jquery Datatables | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/components/image-cropper"> | ||||
|               ImageCropper | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/components/image-zoom"> | ||||
|               ImageZoom | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/components/nestable-list"> | ||||
|               Nestable List | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/components/nestable-tree"> | ||||
|               Nestable Tree | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/components/notifications"> | ||||
|               Notifications | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown active-url="/admin/layouts"> | ||||
|           <template slot="title"> | ||||
|             <a href="#"> | ||||
|               <i class="icon-fa icon-fa-th-large"/>Layouts | ||||
|               <span class="icon-fa arrow icon-fa-fw"/> | ||||
|             </a> | ||||
|           </template> | ||||
|           <v-dropdown-item> | ||||
|             <template slot="item-title"> | ||||
|               <router-link to="/admin/layouts/sidebar"> | ||||
|                 Sidebar | ||||
|               </router-link> | ||||
|             </template> | ||||
|           </v-dropdown-item> | ||||
|           <v-dropdown-item> | ||||
|             <template slot="item-title"> | ||||
|               <router-link to="/admin/layouts/horizontal"> | ||||
|                 Horizontal | ||||
|               </router-link> | ||||
|             </template> | ||||
|           </v-dropdown-item> | ||||
|         </v-dropdown> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/components/sweet-modals"> | ||||
|               Sweet Modals | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/components/image-zoom"> | ||||
|               ImageZoom | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/components/mail-box"> | ||||
|               MailBox | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|       </v-dropdown> | ||||
|       <v-dropdown active-url="/admin/chart"> | ||||
|         <template slot="title"> | ||||
|           <a href="#"> | ||||
|             <i class="icon-fa icon-fa-bar-chart"/>Charts | ||||
|             <span class="icon-fa arrow icon-fa-fw"/> | ||||
|           </a> | ||||
|         </template> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/charts/amchart"> | ||||
|               AM Charts | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/charts/chartjs"> | ||||
|               Chart JS | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/charts/gauge"> | ||||
|               Gauges | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/charts/morris"> | ||||
|               Morris | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/charts/sparkline"> | ||||
|               Sparkline | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|       </v-dropdown> | ||||
|       <v-dropdown active-url="/admin/icons"> | ||||
|         <template slot="title"> | ||||
|           <a href="#"> | ||||
|             <i class="icon-fa icon-fa-eye"/>Icons | ||||
|             <span class="icon-fa arrow icon-fa-fw"/> | ||||
|           </a> | ||||
|         </template> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/icons/icomoon"> | ||||
|               IcoMoon | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/icons/fontawesome"> | ||||
|               Font Awesome | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|       </v-dropdown> | ||||
|       <v-dropdown active-url="/admin/forms"> | ||||
|         <template slot="title"> | ||||
|           <a href="#"> | ||||
|             <i class="icon-fa icon-fa-rocket"/>Form | ||||
|             <span class="icon-fa arrow icon-fa-fw"/> | ||||
|           </a> | ||||
|         </template> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/forms/general"> | ||||
|               General Elements | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/forms/advanced"> | ||||
|               Advanced Elements | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item><v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/forms/layouts"> | ||||
|               Form Layouts | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/forms/validation"> | ||||
|               Form Validation | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/forms/wizards"> | ||||
|               Form Wizard | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/forms/wizards-2"> | ||||
|               Form Wizard 2 | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/forms/wizards-3"> | ||||
|               Form Wizard 3 | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/forms/editors"> | ||||
|               Editors | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/forms/vee"> | ||||
|               Vee Validate | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/forms/vuelidate"> | ||||
|               Vuelidate | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|       </v-dropdown> | ||||
|       <v-dropdown active-url="/admin/gallery"> | ||||
|         <template slot="title"> | ||||
|           <a href="#"> | ||||
|             <i class="icon-fa icon-fa-image"/>Gallery | ||||
|             <span class="icon-fa arrow icon-fa-fw"/> | ||||
|           </a> | ||||
|         </template> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/gallery/grid"> | ||||
|               Grid | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/gallery/masonry-grid"> | ||||
|               Masonry Grid | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|       </v-dropdown> | ||||
|       <v-dropdown active-url="/admin/users"> | ||||
|         <template slot="title"> | ||||
|           <a href="#"> | ||||
|             <i class="icon-fa icon-fa-user"/>Users | ||||
|             <span class="icon-fa arrow icon-fa-fw"/> | ||||
|           </a> | ||||
|         </template> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/users/profile"> | ||||
|               Profile | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|         <v-dropdown-item> | ||||
|           <template slot="item-title"> | ||||
|             <router-link to="/admin/users"> | ||||
|               All Users | ||||
|             </router-link> | ||||
|           </template> | ||||
|         </v-dropdown-item> | ||||
|       </v-dropdown> | ||||
|       <v-dropdown active-url="/admin/todo-item"> | ||||
|         <template slot="title"> | ||||
|           <router-link to="/admin/todo-item"> | ||||
|             <i class="icon-fa icon-fa-check"/>Todos | ||||
|           </router-link> | ||||
|         </template> | ||||
|       </v-dropdown> | ||||
|       <v-dropdown active-url="/admin/settings"> | ||||
|         <template slot="title"> | ||||
|           <router-link to="/admin/settings"> | ||||
|             <i class="icon-fa icon-fa-cogs"/>Settings | ||||
|           </router-link> | ||||
|         </template> | ||||
|       </v-dropdown> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script type="text/babel"> | ||||
| import VDropdown from '../../../components/dropdown/VDropdown' | ||||
| import VDropdownItem from '../../../components/dropdown/VDropdownItem' | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     VDropdown, | ||||
|     VDropdownItem | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       sidebar: 'sidebar' | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| @ -0,0 +1,96 @@ | ||||
| <template> | ||||
|   <div class="sidebar-left"> | ||||
|     <div class="sidebar-body scroll-pane"> | ||||
|       <div class="side-nav"> | ||||
|         <div | ||||
|           v-for="(menuItems, index) in menu" | ||||
|           :key="index" | ||||
|           class="menu-group" | ||||
|         > | ||||
|           <router-link | ||||
|             v-for="(item, index1) in menuItems" | ||||
|             :key="index1" | ||||
|             :to="item.route" | ||||
|             class="menu-item" | ||||
|             @click.native="Toggle" | ||||
|           > | ||||
|             <font-awesome-icon :icon="item.icon" class="icon menu-icon" /> <span class="ml-3 menu-text">{{ $t(item.title) }}</span> | ||||
|           </router-link> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script type="text/babel"> | ||||
| export default { | ||||
|   data () { | ||||
|     return { | ||||
|       sidebar: 'sidebar', | ||||
|       menu: [ | ||||
|  | ||||
|         [ | ||||
|           { | ||||
|             title: 'navigation.dashboard', | ||||
|             icon: 'tachometer-alt', | ||||
|             route: '/admin/dashboard' | ||||
|           }, | ||||
|           { | ||||
|             title: 'navigation.customers', | ||||
|             icon: 'user', | ||||
|             route: '/admin/customers' | ||||
|           }, | ||||
|           { | ||||
|             title: 'navigation.items', | ||||
|             icon: 'star', | ||||
|             route: '/admin/items' | ||||
|           } | ||||
|         ], | ||||
|  | ||||
|         [ | ||||
|           { | ||||
|             title: 'navigation.estimates', | ||||
|             icon: 'file', | ||||
|             route: '/admin/estimates' | ||||
|           }, | ||||
|           { | ||||
|             title: 'navigation.invoices', | ||||
|             icon: 'file-alt', | ||||
|             route: '/admin/invoices' | ||||
|           }, | ||||
|           { | ||||
|             title: 'navigation.payments', | ||||
|             icon: 'credit-card', | ||||
|             route: '/admin/payments' | ||||
|           }, | ||||
|           { | ||||
|             title: 'navigation.expenses', | ||||
|             icon: 'space-shuttle', | ||||
|             route: '/admin/expenses' | ||||
|           } | ||||
|         ], | ||||
|  | ||||
|         [ | ||||
|           { | ||||
|             title: 'navigation.reports', | ||||
|             icon: 'signal', | ||||
|             route: '/admin/reports' | ||||
|           }, | ||||
|           { | ||||
|             title: 'navigation.settings', | ||||
|             icon: 'cog', | ||||
|             route: '/admin/settings' | ||||
|           } | ||||
|         ] | ||||
|  | ||||
|       ] | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   methods: { | ||||
|     Toggle () { | ||||
|       this.$utils.toggleSidebar() | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										368
									
								
								resources/assets/js/views/payments/Create.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										368
									
								
								resources/assets/js/views/payments/Create.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,368 @@ | ||||
| <template> | ||||
|   <div class="payment-create main-content"> | ||||
|     <form action="" @submit.prevent="submitPaymentData"> | ||||
|       <div class="page-header"> | ||||
|         <h3 class="page-title">{{ isEdit ? $t('payments.edit_payment') : $t('payments.new_payment') }}</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/payments">{{ $tc('payments.payment', 2) }}</router-link></li> | ||||
|           <li class="breadcrumb-item">{{ isEdit ? $t('payments.edit_payment') : $t('payments.new_payment') }}</li> | ||||
|         </ol> | ||||
|         <div class="page-actions header-button-container"> | ||||
|           <base-button | ||||
|             :loading="isLoading" | ||||
|             :disabled="isLoading" | ||||
|             icon="save" | ||||
|             color="theme" | ||||
|             type="submit"> | ||||
|             {{ isEdit ? $t('payments.update_payment') : $t('payments.save_payment') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="payment-card card"> | ||||
|         <div class="card-body"> | ||||
|           <div class="row"> | ||||
|             <div class="col-sm-6"> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('payments.date') }}</label><span class="text-danger"> *</span> | ||||
|                 <base-date-picker | ||||
|                   v-model="formData.payment_date" | ||||
|                   :invalid="$v.formData.payment_date.$error" | ||||
|                   :calendar-button="true" | ||||
|                   calendar-button-icon="calendar" | ||||
|                   @change="$v.formData.payment_date.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.formData.payment_date.$error"> | ||||
|                   <span v-if="!$v.formData.payment_date.required" class="text-danger">{{ $t('validation.required') }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="col-sm-6"> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('payments.payment_number') }}</label><span class="text-danger"> *</span> | ||||
|                 <base-input | ||||
|                   :invalid="$v.formData.payment_number.$error" | ||||
|                   v-model.trim="formData.payment_number" | ||||
|                   read-only | ||||
|                   type="text" | ||||
|                   name="email" | ||||
|                   @input="$v.formData.payment_number.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.formData.payment_number.$error"> | ||||
|                   <span v-if="!$v.formData.payment_number.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="col-sm-6"> | ||||
|               <label class="form-label">{{ $t('payments.customer') }}</label><span class="text-danger"> *</span> | ||||
|               <base-select | ||||
|                 ref="baseSelect" | ||||
|                 v-model="customer" | ||||
|                 :invalid="$v.customer.$error" | ||||
|                 :options="customerList" | ||||
|                 :searchable="true" | ||||
|                 :show-labels="false" | ||||
|                 :allow-empty="false" | ||||
|                 :disabled="isEdit" | ||||
|                 :placeholder="$t('customers.select_a_customer')" | ||||
|                 label="name" | ||||
|                 track-by="id" | ||||
|               /> | ||||
|               <div v-if="$v.customer.$error"> | ||||
|                 <span v-if="!$v.customer.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="col-sm-6"> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('payments.invoice') }}</label> | ||||
|                 <base-select | ||||
|                   v-model="invoice" | ||||
|                   :options="invoiceList" | ||||
|                   :searchable="true" | ||||
|                   :show-labels="false" | ||||
|                   :allow-empty="false" | ||||
|                   :disabled="isEdit" | ||||
|                   :placeholder="$t('invoices.select_invoice')" | ||||
|                   label="invoice_number" | ||||
|                   track-by="invoice_number" | ||||
|                 /> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="col-sm-6"> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('payments.amount') }}</label><span class="text-danger"> *</span> | ||||
|                 <div class="base-input"> | ||||
|                   <money | ||||
|                     :class="{'invalid' : $v.formData.amount.$error}" | ||||
|                     v-model="amount" | ||||
|                     v-bind="customerCurrency" | ||||
|                     class="input-field" | ||||
|                   /> | ||||
|                 </div> | ||||
|                 <div v-if="$v.formData.amount.$error"> | ||||
|                   <span v-if="!$v.formData.amount.required" class="text-danger">{{ $t('validation.required') }}</span> | ||||
|                   <span v-if="!$v.formData.amount.numeric" class="text-danger">{{ $t('validation.numbers_only') }}</span> | ||||
|                   <span v-if="!$v.formData.amount.between && $v.formData.amount.numeric && amount <= 0" class="text-danger">{{ $t('validation.payment_greater_than_zero') }}</span> | ||||
|                   <span v-if="!$v.formData.amount.between && amount > 0" class="text-danger">{{ $t('validation.payment_grater_than_due_amount') }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="col-sm-6"> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('payments.payment_mode') }}</label> | ||||
|                 <base-select | ||||
|                   v-model="formData.payment_mode" | ||||
|                   :options="getPaymentMode" | ||||
|                   :searchable="true" | ||||
|                   :show-labels="false" | ||||
|                   :placeholder="$t('payments.select_payment_mode')" | ||||
|                 /> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="col-sm-12 "> | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('payments.note') }}</label> | ||||
|                 <base-text-area | ||||
|                   v-model="formData.notes" | ||||
|                   @input="$v.formData.notes.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.formData.notes.$error"> | ||||
|                   <span v-if="!$v.formData.notes.maxLength" class="text-danger">{{ $t('validation.notes_maxlength') }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="col-sm-12"> | ||||
|               <div class="form-group collapse-button-container"> | ||||
|                 <base-button | ||||
|                   :loading="isLoading" | ||||
|                   icon="save" | ||||
|                   color="theme" | ||||
|                   type="submit" | ||||
|                   class="collapse-button" | ||||
|                 > | ||||
|                   {{ $t('payments.save_payment') }} | ||||
|                 </base-button> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </form> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
| import MultiSelect from 'vue-multiselect' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import moment from 'moment' | ||||
| const { required, numeric, between, maxLength } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   components: { MultiSelect }, | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       formData: { | ||||
|         user_id: null, | ||||
|         payment_number: null, | ||||
|         payment_date: null, | ||||
|         amount: 100, | ||||
|         payment_mode: null, | ||||
|         invoice_id: null, | ||||
|         notes: null | ||||
|       }, | ||||
|       money: { | ||||
|         decimal: '.', | ||||
|         thousands: ',', | ||||
|         prefix: '$ ', | ||||
|         precision: 2, | ||||
|         masked: false | ||||
|       }, | ||||
|       customer: null, | ||||
|       invoice: null, | ||||
|       customerList: [], | ||||
|       invoiceList: [], | ||||
|       isLoading: false, | ||||
|       maxPayableAmount: Number.MAX_SAFE_INTEGER | ||||
|     } | ||||
|   }, | ||||
|   validations () { | ||||
|     return { | ||||
|       customer: { | ||||
|         required | ||||
|       }, | ||||
|       formData: { | ||||
|         payment_number: { | ||||
|           required | ||||
|         }, | ||||
|         payment_date: { | ||||
|           required | ||||
|         }, | ||||
|         amount: { | ||||
|           required, | ||||
|           numeric, | ||||
|           between: between(1, this.maxPayableAmount + 1) | ||||
|         }, | ||||
|         notes: { | ||||
|           maxLength: maxLength(255) | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('currency', [ | ||||
|       'defaultCurrencyForInput' | ||||
|     ]), | ||||
|     getPaymentMode () { | ||||
|       return ['Cash', 'Check', 'Credit Card', 'Bank Transfer'] | ||||
|     }, | ||||
|     amount: { | ||||
|       get: function () { | ||||
|         return this.formData.amount / 100 | ||||
|       }, | ||||
|       set: function (newValue) { | ||||
|         this.formData.amount = newValue * 100 | ||||
|       } | ||||
|     }, | ||||
|     isEdit () { | ||||
|       if (this.$route.name === 'payments.edit') { | ||||
|         return true | ||||
|       } | ||||
|       return false | ||||
|     }, | ||||
|     customerCurrency () { | ||||
|       if (this.customer && this.customer.currency) { | ||||
|         return { | ||||
|           decimal: this.customer.currency.decimal_separator, | ||||
|           thousands: this.customer.currency.thousand_separator, | ||||
|           prefix: this.customer.currency.symbol + ' ', | ||||
|           precision: this.customer.currency.precision, | ||||
|           masked: false | ||||
|         } | ||||
|       } else { | ||||
|         return this.defaultCurrencyForInput | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     customer (newValue) { | ||||
|       this.formData.user_id = newValue.id | ||||
|       if (!this.isEdit) { | ||||
|         this.fetchCustomerInvoices(newValue.id) | ||||
|       } | ||||
|     }, | ||||
|     invoice (newValue) { | ||||
|       this.formData.invoice_id = newValue.id | ||||
|       if (!this.isEdit) { | ||||
|         this.setPaymentAmountByInvoiceData(newValue.id) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   async mounted () { | ||||
|     // if (!this.$route.params.id) { | ||||
|     //   this.$refs.baseSelect.$refs.search.focus() | ||||
|     // } | ||||
|     this.$nextTick(() => { | ||||
|       this.loadData() | ||||
|       if (this.$route.params.id && !this.isEdit) { | ||||
|         this.setInvoicePaymentData() | ||||
|       } | ||||
|     }) | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('invoice', [ | ||||
|       'fetchInvoice' | ||||
|     ]), | ||||
|     ...mapActions('payment', [ | ||||
|       'fetchCreatePayment', | ||||
|       'addPayment', | ||||
|       'updatePayment', | ||||
|       'fetchPayment' | ||||
|     ]), | ||||
|     async loadData () { | ||||
|       if (this.isEdit) { | ||||
|         let response = await this.fetchPayment(this.$route.params.id) | ||||
|         this.customerList = response.data.customers | ||||
|         this.formData = { ...response.data.payment } | ||||
|         this.customer = response.data.payment.user | ||||
|         this.formData.payment_date = moment(response.data.payment.payment_date, 'YYYY-MM-DD').toString() | ||||
|         this.formData.amount = parseFloat(response.data.payment.amount) | ||||
|         this.maxPayableAmount = response.data.payment.amount | ||||
|         if (response.data.payment.invoice !== null) { | ||||
|           this.maxPayableAmount = parseInt(response.data.payment.amount) + parseInt(response.data.payment.invoice.due_amount) | ||||
|           this.invoice = response.data.payment.invoice | ||||
|         } | ||||
|         // this.fetchCustomerInvoices(this.customer.id) | ||||
|       } else { | ||||
|         let response = await this.fetchCreatePayment() | ||||
|         this.customerList = response.data.customers | ||||
|         this.formData.payment_number = response.data.nextPaymentNumber | ||||
|         this.formData.payment_date = moment(new Date()).toString() | ||||
|       } | ||||
|       return true | ||||
|     }, | ||||
|     async setInvoicePaymentData () { | ||||
|       let data = await this.fetchInvoice(this.$route.params.id) | ||||
|       this.customer = data.data.invoice.user | ||||
|       this.invoice = data.data.invoice | ||||
|     }, | ||||
|     async setPaymentAmountByInvoiceData (id) { | ||||
|       let data = await this.fetchInvoice(id) | ||||
|       this.formData.amount = data.data.invoice.due_amount | ||||
|       this.maxPayableAmount = data.data.invoice.due_amount | ||||
|     }, | ||||
|     async fetchCustomerInvoices (userID) { | ||||
|       let response = await axios.get(`/api/invoices/unpaid/${userID}`) | ||||
|       if (response.data) { | ||||
|         this.invoiceList = response.data.invoices | ||||
|       } | ||||
|     }, | ||||
|     async submitPaymentData () { | ||||
|       this.$v.customer.$touch() | ||||
|       this.$v.formData.$touch() | ||||
|       if (this.$v.$invalid) { | ||||
|         return true | ||||
|       } | ||||
|       if (this.isEdit) { | ||||
|         let data = { | ||||
|           editData: { | ||||
|             ...this.formData, | ||||
|             payment_date: moment(this.formData.payment_date).format('DD/MM/YYYY') | ||||
|           }, | ||||
|           id: this.$route.params.id | ||||
|         } | ||||
|         let response = await this.updatePayment(data) | ||||
|         if (response.data.success) { | ||||
|           window.toastr['success'](this.$t('payments.updated_message')) | ||||
|           this.$router.push('/admin/payments') | ||||
|           return true | ||||
|         } | ||||
|         if (response.data.error === 'invalid_amount') { | ||||
|           window.toastr['error'](this.$t('invalid_amount_message')) | ||||
|           return false | ||||
|         } | ||||
|         window.toastr['error'](response.data.error) | ||||
|       } else { | ||||
|         let data = { | ||||
|           ...this.formData, | ||||
|           payment_date: moment(this.formData.payment_date).format('DD/MM/YYYY') | ||||
|         } | ||||
|         this.isLoading = true | ||||
|         let response = await this.addPayment(data) | ||||
|         if (response.data.success) { | ||||
|           window.toastr['success'](this.$t('payments.created_message')) | ||||
|           this.$router.push('/admin/payments') | ||||
|           this.isLoading = true | ||||
|           return true | ||||
|         } | ||||
|         if (response.data.error === 'invalid_amount') { | ||||
|           window.toastr['error'](this.$t('invalid_amount_message')) | ||||
|           return false | ||||
|         } | ||||
|         window.toastr['error'](response.data.error) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										423
									
								
								resources/assets/js/views/payments/Index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										423
									
								
								resources/assets/js/views/payments/Index.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,423 @@ | ||||
| <template> | ||||
|   <div class="payments main-content"> | ||||
|     <div class="page-header"> | ||||
|       <h3 class="page-title">{{ $t('payments.title') }}</h3> | ||||
|       <ol class="breadcrumb"> | ||||
|         <li class="breadcrumb-item"> | ||||
|           <router-link | ||||
|             slot="item-title" | ||||
|             to="dashboard"> | ||||
|             {{ $t('general.home') }} | ||||
|           </router-link> | ||||
|         </li> | ||||
|         <li class="breadcrumb-item"> | ||||
|           <router-link | ||||
|             slot="item-title" | ||||
|             to="#"> | ||||
|             {{ $tc('payments.payment',2) }} | ||||
|           </router-link> | ||||
|         </li> | ||||
|       </ol> | ||||
|       <div class="page-actions row"> | ||||
|         <div class="col-xs-2 mr-4"> | ||||
|           <base-button | ||||
|             v-show="totalPayments || filtersApplied" | ||||
|             :outline="true" | ||||
|             :icon="filterIcon" | ||||
|             color="theme" | ||||
|             right-icon | ||||
|             size="large" | ||||
|             @click="toggleFilter" | ||||
|           > | ||||
|             {{ $t('general.filter') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|         <router-link slot="item-title" class="col-xs-2" to="payments/create"> | ||||
|           <base-button | ||||
|             color="theme" | ||||
|             icon="plus" | ||||
|             size="large" | ||||
|           > | ||||
|             {{ $t('payments.add_payment') }} | ||||
|           </base-button> | ||||
|         </router-link> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <transition name="fade" mode="out-in"> | ||||
|       <div v-show="showFilters" class="filter-section"> | ||||
|         <div class="row"> | ||||
|           <div class="col-md-4"> | ||||
|             <label class="form-label">{{ $t('payments.customer') }}</label> | ||||
|             <base-customer-select | ||||
|               ref="customerSelect" | ||||
|               @select="onSelectCustomer" | ||||
|               @deselect="clearCustomerSearch" | ||||
|             /> | ||||
|           </div> | ||||
|           <div class="col-sm-4"> | ||||
|             <label for="">{{ $t('payments.payment_number') }}</label> | ||||
|             <base-input | ||||
|               v-model="filters.payment_number" | ||||
|               :placeholder="$t(payments.payment_number)" | ||||
|               name="payment_number" | ||||
|             /> | ||||
|           </div> | ||||
|           <div class="col-sm-4"> | ||||
|             <label class="form-label">{{ $t('payments.payment_mode') }}</label> | ||||
|             <base-select | ||||
|               v-model="filters.payment_mode" | ||||
|               :options="payment_mode" | ||||
|               :searchable="true" | ||||
|               :show-labels="false" | ||||
|               :placeholder="$t('payments.payment_mode')" | ||||
|             /> | ||||
|           </div> | ||||
|         </div> | ||||
|         <label class="clear-filter" @click="clearFilter">{{ $t('general.clear_all') }}</label> | ||||
|       </div> | ||||
|     </transition> | ||||
|  | ||||
|     <div v-cloak v-show="showEmptyScreen" class="col-xs-1 no-data-info" align="center"> | ||||
|       <capsule-icon class="mt-5 mb-4"/> | ||||
|       <div class="row" align="center"> | ||||
|         <label class="col title">{{ $t('payments.no_payments') }}</label> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <label class="description col mt-1" align="center">{{ $t('payments.list_of_payments') }}</label> | ||||
|       </div> | ||||
|       <div class="btn-container"> | ||||
|         <base-button | ||||
|           :outline="true" | ||||
|           color="theme" | ||||
|           class="mt-3" | ||||
|           size="large" | ||||
|           @click="$router.push('payments/create')" | ||||
|         > | ||||
|           {{ $t('payments.add_new_payment') }} | ||||
|         </base-button> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <div v-show="!showEmptyScreen" class="table-container"> | ||||
|       <div class="table-actions mt-5"> | ||||
|         <p class="table-stats">{{ $t('general.showing') }}: <b>{{ payments.length }}</b> {{ $t('general.of') }} <b>{{ totalPayments }}</b></p> | ||||
|  | ||||
|         <transition name="fade"> | ||||
|           <v-dropdown v-if="selectedPayments.length" :show-arrow="false"> | ||||
|             <span slot="activator" href="#" class="table-actions-button dropdown-toggle"> | ||||
|               {{ $t('general.actions') }} | ||||
|             </span> | ||||
|             <v-dropdown-item> | ||||
|               <div class="dropdown-item" @click="removeMultiplePayments"> | ||||
|                 <font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" /> | ||||
|                 {{ $t('general.delete') }} | ||||
|               </div> | ||||
|             </v-dropdown-item> | ||||
|           </v-dropdown> | ||||
|         </transition> | ||||
|       </div> | ||||
|  | ||||
|       <div class="custom-control custom-checkbox"> | ||||
|         <input | ||||
|           id="select-all" | ||||
|           v-model="selectAllFieldStatus" | ||||
|           type="checkbox" | ||||
|           class="custom-control-input" | ||||
|           @change="selectAllPayments" | ||||
|         > | ||||
|         <label v-show="!isRequestOngoing" for="select-all" class="custom-control-label selectall"> | ||||
|           <span class="select-all-label">{{ $t('general.select_all') }} </span> | ||||
|         </label> | ||||
|       </div> | ||||
|  | ||||
|       <table-component | ||||
|         ref="table" | ||||
|         :data="fetchData" | ||||
|         :show-filter="false" | ||||
|         table-class="table" | ||||
|       > | ||||
|         <table-column | ||||
|           :sortable="false" | ||||
|           :filterable="false" | ||||
|           cell-class="no-click" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <div class="custom-control custom-checkbox"> | ||||
|               <input | ||||
|                 :id="row.id" | ||||
|                 v-model="selectField" | ||||
|                 :value="row.id" | ||||
|                 type="checkbox" | ||||
|                 class="custom-control-input" | ||||
|               > | ||||
|               <label :for="row.id" class="custom-control-label" /> | ||||
|             </div> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <table-column | ||||
|           :label="$t('payments.date')" | ||||
|           sort-as="payment_date" | ||||
|           show="formattedPaymentDate" | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('payments.customer')" | ||||
|           show="name" | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('payments.payment_mode')" | ||||
|           show="payment_mode" | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('payments.payment_number')" | ||||
|           show="payment_number" | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('payments.invoice')" | ||||
|           sort-as="invoice_id" | ||||
|           show="invoice.invoice_number" | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('payments.amount')" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <span>{{ $t('payments.amount') }}</span> | ||||
|             <div v-html="$utils.formatMoney(row.amount, row.user.currency)" /> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <table-column | ||||
|           :sortable="false" | ||||
|           :filterable="false" | ||||
|           cell-class="action-dropdown no-click" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <span>{{ $t('payments.action') }}</span> | ||||
|             <v-dropdown> | ||||
|               <a slot="activator" href="#"> | ||||
|                 <dot-icon /> | ||||
|               </a> | ||||
|               <v-dropdown-item> | ||||
|  | ||||
|                 <router-link :to="{path: `payments/${row.id}/edit`}" class="dropdown-item"> | ||||
|                   <font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon" /> | ||||
|                   {{ $t('general.edit') }} | ||||
|                 </router-link> | ||||
|  | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item> | ||||
|                 <div class="dropdown-item" @click="removePayment(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' | ||||
| import { SweetModal, SweetModalTab } from 'sweet-modal-vue' | ||||
| import CapsuleIcon from '../../components/icon/CapsuleIcon' | ||||
| import BaseButton from '../../../js/components/base/BaseButton' | ||||
| import { request } from 'http' | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     'capsule-icon': CapsuleIcon, | ||||
|     'SweetModal': SweetModal, | ||||
|     'SweetModalTab': SweetModalTab, | ||||
|     BaseButton | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       showFilters: false, | ||||
|       sortedBy: 'created_at', | ||||
|       filtersApplied: false, | ||||
|       isRequestOngoing: true, | ||||
|       payment_mode: ['Cash', 'Check', 'Credit Card', 'Bank Transfer'], | ||||
|       filters: { | ||||
|         customer: null, | ||||
|         payment_mode: '', | ||||
|         payment_number: '' | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     showEmptyScreen () { | ||||
|       return !this.totalPayments && !this.isRequestOngoing && !this.filtersApplied | ||||
|     }, | ||||
|     filterIcon () { | ||||
|       return (this.showFilters) ? 'times' : 'filter' | ||||
|     }, | ||||
|     ...mapGetters('customer', [ | ||||
|       'customers' | ||||
|     ]), | ||||
|     ...mapGetters('payment', [ | ||||
|       'selectedPayments', | ||||
|       'totalPayments', | ||||
|       'payments', | ||||
|       'selectAllField' | ||||
|     ]), | ||||
|     selectField: { | ||||
|       get: function () { | ||||
|         return this.selectedPayments | ||||
|       }, | ||||
|       set: function (val) { | ||||
|         this.selectPayment(val) | ||||
|       } | ||||
|     }, | ||||
|     selectAllFieldStatus: { | ||||
|       get: function () { | ||||
|         return this.selectAllField | ||||
|       }, | ||||
|       set: function (val) { | ||||
|         this.setSelectAllState(val) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     filters: { | ||||
|       handler: 'setFilters', | ||||
|       deep: true | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     this.fetchCustomers() | ||||
|   }, | ||||
|   destroyed () { | ||||
|     if (this.selectAllField) { | ||||
|       this.selectAllPayments() | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('payment', [ | ||||
|       'fetchPayments', | ||||
|       'selectAllPayments', | ||||
|       'selectPayment', | ||||
|       'deletePayment', | ||||
|       'deleteMultiplePayments', | ||||
|       'setSelectAllState' | ||||
|     ]), | ||||
|     ...mapActions('customer', [ | ||||
|       'fetchCustomers' | ||||
|     ]), | ||||
|     async fetchData ({ page, filter, sort }) { | ||||
|       let data = { | ||||
|         customer_id: this.filters.customer !== null ? this.filters.customer.id : '', | ||||
|         payment_number: this.filters.payment_number, | ||||
|         payment_mode: this.filters.payment_mode ? this.filters.payment_mode : '', | ||||
|         orderByField: sort.fieldName || 'created_at', | ||||
|         orderBy: sort.order || 'desc', | ||||
|         page | ||||
|       } | ||||
|  | ||||
|       this.isRequestOngoing = true | ||||
|  | ||||
|       let response = await this.fetchPayments(data) | ||||
|  | ||||
|       this.isRequestOngoing = false | ||||
|  | ||||
|       return { | ||||
|         data: response.data.payments.data, | ||||
|         pagination: { | ||||
|           totalPages: response.data.payments.last_page, | ||||
|           currentPage: page, | ||||
|           count: response.data.payments.scount | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     refreshTable () { | ||||
|       this.$refs.table.refresh() | ||||
|     }, | ||||
|     setFilters () { | ||||
|       this.filtersApplied = true | ||||
|       this.refreshTable() | ||||
|     }, | ||||
|     clearFilter () { | ||||
|       if (this.filters.customer) { | ||||
|         this.$refs.customerSelect.$refs.baseSelect.removeElement(this.filters.customer) | ||||
|       } | ||||
|  | ||||
|       this.filters = { | ||||
|         customer: null, | ||||
|         payment_mode: '', | ||||
|         payment_number: '' | ||||
|       } | ||||
|  | ||||
|       this.$nextTick(() => { | ||||
|         this.filtersApplied = false | ||||
|       }) | ||||
|     }, | ||||
|     toggleFilter () { | ||||
|       if (this.showFilters && this.filtersApplied) { | ||||
|         this.clearFilter() | ||||
|         this.refreshTable() | ||||
|       } | ||||
|  | ||||
|       this.showFilters = !this.showFilters | ||||
|     }, | ||||
|     onSelectCustomer (customer) { | ||||
|       this.filters.customer = customer | ||||
|     }, | ||||
|     async removePayment (id) { | ||||
|       this.id = id | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$tc('payments.confirm_delete'), | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let res = await this.deletePayment(this.id) | ||||
|           if (res.data.success) { | ||||
|             window.toastr['success'](this.$tc('payments.deleted_message', 1)) | ||||
|             this.$refs.table.refresh() | ||||
|             return true | ||||
|           } else if (res.data.error) { | ||||
|             window.toastr['error'](res.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     async removeMultiplePayments () { | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$tc('payments.confirm_delete', 2), | ||||
|         icon: 'error', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let request = await this.deleteMultiplePayments() | ||||
|           if (request.data.success) { | ||||
|             window.toastr['success'](this.$tc('payments.deleted_message', 2)) | ||||
|             this.$refs.table.refresh() | ||||
|           } else if (request.data.error) { | ||||
|             window.toastr['error'](request.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     async clearCustomerSearch (removedOption, id) { | ||||
|       this.filters.customer = '' | ||||
|       this.$refs.table.refresh() | ||||
|     }, | ||||
|     showModel (selectedRow) { | ||||
|       this.selectedRow = selectedRow | ||||
|       this.$refs.Delete_modal.open() | ||||
|     }, | ||||
|     async removeSelectedItems () { | ||||
|       this.$refs.Delete_modal.close() | ||||
|       await this.selectedRow.forEach(row => { | ||||
|         this.deletePayment(this.id) | ||||
|       }) | ||||
|       this.$refs.table.refresh() | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										201
									
								
								resources/assets/js/views/reports/ExpensesReport.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								resources/assets/js/views/reports/ExpensesReport.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,201 @@ | ||||
| <template> | ||||
|   <div class="row"> | ||||
|     <div class="col-md-4 reports-tab-container"> | ||||
|       <div class="row"> | ||||
|         <div class="col-md-8"> | ||||
|           <label class="report-label">{{ $t('reports.expenses.date_range') }}</label> | ||||
|           <base-select | ||||
|             v-model="selectedRange" | ||||
|             :options="dateRange" | ||||
|             :allow-empty="false" | ||||
|             :show-labels="false" | ||||
|             @input="onChangeDateRange" | ||||
|           /> | ||||
|           <span v-if="$v.range.$error && !$v.range.required" class="text-danger"> {{ $t('validation.required') }} </span> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row report-fields-container"> | ||||
|         <div class="col-md-6 report-field-container"> | ||||
|           <label class="report-label">{{ $t('reports.expenses.from_date') }}</label> | ||||
|           <base-date-picker | ||||
|             v-model="formData.from_date" | ||||
|             :invalid="$v.formData.from_date.$error" | ||||
|             :calendar-button="true" | ||||
|             calendar-button-icon="calendar" | ||||
|             @change="$v.formData.from_date.$touch()" | ||||
|           /> | ||||
|           <span v-if="$v.formData.from_date.$error && !$v.formData.from_date.required" class="text-danger"> {{ $t('validation.required') }} </span> | ||||
|         </div> | ||||
|         <div class="col-md-6 report-field-container"> | ||||
|           <label class="report-label">{{ $t('reports.expenses.to_date') }}</label> | ||||
|           <base-date-picker | ||||
|             v-model="formData.to_date" | ||||
|             :invalid="$v.formData.to_date.$error" | ||||
|             :calendar-button="true" | ||||
|             calendar-button-icon="calendar" | ||||
|             @change="$v.formData.to_date.$touch()" | ||||
|           /> | ||||
|           <span v-if="$v.formData.to_date.$error && !$v.formData.to_date.required" class="text-danger"> {{ $t('validation.required') }} </span> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row report-submit-button-container"> | ||||
|         <div class="col-md-6"> | ||||
|           <base-button outline color="theme" class="report-button" @click="getReports()"> | ||||
|             {{ $t('reports.update_report') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="col-sm-8 reports-tab-container"> | ||||
|       <iframe :src="getReportUrl" class="reports-frame-style"/> | ||||
|       <a :href="getReportUrl" class="base-button btn btn-primary btn-lg report-view-button" target="_blank"> | ||||
|         <font-awesome-icon icon="file-pdf" class="vue-icon icon-left svg-inline--fa fa-download fa-w-16 mr-2" /> <span>{{ $t('reports.view_pdf') }}</span> | ||||
|       </a> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapGetters } from 'vuex' | ||||
| import moment from 'moment' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| const { required } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       range: new Date(), | ||||
|       dateRange: [ | ||||
|         'Today', | ||||
|         'This Week', | ||||
|         'This Month', | ||||
|         'This Quarter', | ||||
|         'This Year', | ||||
|         'Previous Week', | ||||
|         'Previous Month', | ||||
|         'Previous Quarter', | ||||
|         'Previous Year', | ||||
|         'Custom' | ||||
|       ], | ||||
|       selectedRange: 'This Month', | ||||
|       formData: { | ||||
|         from_date: moment().startOf('month').toString(), | ||||
|         to_date: moment().endOf('month').toString() | ||||
|       }, | ||||
|       url: null, | ||||
|       siteURL: null | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     range: { | ||||
|       required | ||||
|     }, | ||||
|     formData: { | ||||
|       from_date: { | ||||
|         required | ||||
|       }, | ||||
|       to_date: { | ||||
|         required | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('company', [ | ||||
|       'getSelectedCompany' | ||||
|     ]), | ||||
|     getReportUrl () { | ||||
|       return this.url | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     range (newRange) { | ||||
|       this.formData.from_date = moment(newRange).startOf('year').toString() | ||||
|       this.formData.to_date = moment(newRange).endOf('year').toString() | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     this.siteURL = `/reports/expenses/${this.getSelectedCompany.unique_hash}` | ||||
|     this.url = `${this.siteURL}?from_date=${moment(this.formData.from_date).format('DD/MM/YYYY')}&to_date=${moment(this.formData.to_date).format('DD/MM/YYYY')}` | ||||
|   }, | ||||
|   methods: { | ||||
|     getThisDate (type, time) { | ||||
|       return moment()[type](time).toString() | ||||
|     }, | ||||
|     getPreDate (type, time) { | ||||
|       return moment().subtract(1, time)[type](time).toString() | ||||
|     }, | ||||
|     onChangeDateRange () { | ||||
|       switch (this.selectedRange) { | ||||
|         case 'Today': | ||||
|           this.formData.from_date = moment().toString() | ||||
|           this.formData.to_date = moment().toString() | ||||
|           break | ||||
|  | ||||
|         case 'This Week': | ||||
|           this.formData.from_date = this.getThisDate('startOf', 'isoWeek') | ||||
|           this.formData.to_date = this.getThisDate('endOf', 'isoWeek') | ||||
|           break | ||||
|  | ||||
|         case 'This Month': | ||||
|           this.formData.from_date = this.getThisDate('startOf', 'month') | ||||
|           this.formData.to_date = this.getThisDate('endOf', 'month') | ||||
|           break | ||||
|  | ||||
|         case 'This Quarter': | ||||
|           this.formData.from_date = this.getThisDate('startOf', 'quarter') | ||||
|           this.formData.to_date = this.getThisDate('endOf', 'quarter') | ||||
|           break | ||||
|  | ||||
|         case 'This Year': | ||||
|           this.formData.from_date = this.getThisDate('startOf', 'year') | ||||
|           this.formData.to_date = this.getThisDate('endOf', 'year') | ||||
|           break | ||||
|  | ||||
|         case 'Previous Week': | ||||
|           this.formData.from_date = this.getPreDate('startOf', 'isoWeek') | ||||
|           this.formData.to_date = this.getPreDate('endOf', 'isoWeek') | ||||
|           break | ||||
|  | ||||
|         case 'Previous Month': | ||||
|           this.formData.from_date = this.getPreDate('startOf', 'month') | ||||
|           this.formData.to_date = this.getPreDate('endOf', 'month') | ||||
|           break | ||||
|  | ||||
|         case 'Previous Quarter': | ||||
|           this.formData.from_date = this.getPreDate('startOf', 'quarter') | ||||
|           this.formData.to_date = this.getPreDate('endOf', 'quarter') | ||||
|           break | ||||
|  | ||||
|         case 'Previous Year': | ||||
|           this.formData.from_date = this.getPreDate('startOf', 'year') | ||||
|           this.formData.to_date = this.getPreDate('endOf', 'year') | ||||
|           break | ||||
|  | ||||
|         default: | ||||
|           break | ||||
|       } | ||||
|     }, | ||||
|     setRangeToCustom () { | ||||
|       this.selectedRange = 'Custom' | ||||
|     }, | ||||
|     async getReports (isDownload = false) { | ||||
|       this.$v.range.$touch() | ||||
|       this.$v.formData.$touch() | ||||
|  | ||||
|       if (this.$v.$invalid) { | ||||
|         return true | ||||
|       } | ||||
|  | ||||
|       this.url = `${this.siteURL}?from_date=${moment(this.formData.from_date).format('DD/MM/YYYY')}&to_date=${moment(this.formData.to_date).format('DD/MM/YYYY')}` | ||||
|       return true | ||||
|     }, | ||||
|     downloadReport () { | ||||
|       this.url += '&download=true' | ||||
|       setTimeout(() => { | ||||
|         this.url = `${this.siteURL}?from_date=${moment(this.formData.from_date).format('DD/MM/YYYY')}&to_date=${moment(this.formData.to_date).format('DD/MM/YYYY')}` | ||||
|       }, 200) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										205
									
								
								resources/assets/js/views/reports/ProfitLossReport.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								resources/assets/js/views/reports/ProfitLossReport.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,205 @@ | ||||
| <template> | ||||
|   <div class="row"> | ||||
|     <div class="col-md-4 reports-tab-container"> | ||||
|       <div class="row"> | ||||
|         <div class="col-md-8"> | ||||
|           <label class="report-label">{{ $t('reports.profit_loss.date_range') }}</label> | ||||
|           <base-select | ||||
|             v-model="selectedRange" | ||||
|             :options="dateRange" | ||||
|             :allow-empty="false" | ||||
|             :show-labels="false" | ||||
|             @input="onChangeDateRange" | ||||
|           /> | ||||
|           <span v-if="$v.range.$error && !$v.range.required" class="text-danger"> {{ $t('validation.required') }} </span> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row report-fields-container"> | ||||
|         <div class="col-md-6 report-field-container"> | ||||
|           <label class="report-label">{{ $t('reports.profit_loss.from_date') }}</label> | ||||
|           <base-date-picker | ||||
|             v-model="formData.from_date" | ||||
|             :invalid="$v.formData.from_date.$error" | ||||
|             :calendar-button="true" | ||||
|             calendar-button-icon="calendar" | ||||
|             @change="$v.formData.from_date.$touch()" | ||||
|           /> | ||||
|           <span v-if="$v.formData.from_date.$error && !$v.formData.from_date.required" class="text-danger"> {{ $t('validation.required') }} </span> | ||||
|         </div> | ||||
|         <div class="col-md-6 report-field-container"> | ||||
|           <label class="report-label">{{ $t('reports.profit_loss.to_date') }}</label> | ||||
|           <base-date-picker | ||||
|             v-model="formData.to_date" | ||||
|             :invalid="$v.formData.to_date.$error" | ||||
|             :calendar-button="true" | ||||
|             calendar-button-icon="calendar" | ||||
|             @change="$v.formData.to_date.$touch()" | ||||
|           /> | ||||
|           <span v-if="$v.formData.to_date.$error && !$v.formData.to_date.required" class="text-danger"> {{ $t('validation.required') }} </span> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row report-submit-button-container"> | ||||
|         <div class="col-md-6"> | ||||
|           <base-button outline color="theme" class="report-button" @click="getReports()"> | ||||
|             {{ $t('reports.update_report') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="col-sm-8 reports-tab-container"> | ||||
|       <iframe :src="getReportUrl" class="reports-frame-style"/> | ||||
|       <a :href="getReportUrl" class="base-button btn btn-primary btn-lg report-view-button" target="_blank"> | ||||
|         <font-awesome-icon icon="file-pdf" class="vue-icon icon-left svg-inline--fa fa-download fa-w-16 mr-2" /> <span>{{ $t('reports.view_pdf') }}</span> | ||||
|       </a> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapGetters, mapActions } from 'vuex' | ||||
| import moment from 'moment' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| const { required } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       dateRange: [ | ||||
|         'Today', | ||||
|         'This Week', | ||||
|         'This Month', | ||||
|         'This Quarter', | ||||
|         'This Year', | ||||
|         'Previous Week', | ||||
|         'Previous Month', | ||||
|         'Previous Quarter', | ||||
|         'Previous Year', | ||||
|         'Custom' | ||||
|       ], | ||||
|       selectedRange: 'This Month', | ||||
|       range: new Date(), | ||||
|       formData: { | ||||
|         from_date: moment().startOf('month').toString(), | ||||
|         to_date: moment().endOf('month').toString() | ||||
|       }, | ||||
|       url: null, | ||||
|       siteURL: null | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     range: { | ||||
|       required | ||||
|     }, | ||||
|     formData: { | ||||
|       from_date: { | ||||
|         required | ||||
|       }, | ||||
|       to_date: { | ||||
|         required | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('company', [ | ||||
|       'getSelectedCompany' | ||||
|     ]), | ||||
|     getReportUrl () { | ||||
|       return this.url | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     range (newRange) { | ||||
|       this.formData.from_date = moment(newRange).startOf('year').toString() | ||||
|       this.formData.to_date = moment(newRange).endOf('year').toString() | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     this.siteURL = `/reports/profit-loss/${this.getSelectedCompany.unique_hash}` | ||||
|     this.url = `${this.siteURL}?from_date=${moment(this.formData.from_date).format('DD/MM/YYYY')}&to_date=${moment(this.formData.to_date).format('DD/MM/YYYY')}` | ||||
|  | ||||
|     this.loadProfitLossLink(this.url + '&download=true') | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('profitLossReport',[ | ||||
|       'loadProfitLossLink' | ||||
|     ]), | ||||
|     getThisDate (type, time) { | ||||
|       return moment()[type](time).toString() | ||||
|     }, | ||||
|     getPreDate (type, time) { | ||||
|       return moment().subtract(1, time)[type](time).toString() | ||||
|     }, | ||||
|     onChangeDateRange () { | ||||
|       switch (this.selectedRange) { | ||||
|         case 'Today': | ||||
|           this.formData.from_date = moment().toString() | ||||
|           this.formData.to_date = moment().toString() | ||||
|           break | ||||
|  | ||||
|         case 'This Week': | ||||
|           this.formData.from_date = this.getThisDate('startOf', 'isoWeek') | ||||
|           this.formData.to_date = this.getThisDate('endOf', 'isoWeek') | ||||
|           break | ||||
|  | ||||
|         case 'This Month': | ||||
|           this.formData.from_date = this.getThisDate('startOf', 'month') | ||||
|           this.formData.to_date = this.getThisDate('endOf', 'month') | ||||
|           break | ||||
|  | ||||
|         case 'This Quarter': | ||||
|           this.formData.from_date = this.getThisDate('startOf', 'quarter') | ||||
|           this.formData.to_date = this.getThisDate('endOf', 'quarter') | ||||
|           break | ||||
|  | ||||
|         case 'This Year': | ||||
|           this.formData.from_date = this.getThisDate('startOf', 'year') | ||||
|           this.formData.to_date = this.getThisDate('endOf', 'year') | ||||
|           break | ||||
|  | ||||
|         case 'Previous Week': | ||||
|           this.formData.from_date = this.getPreDate('startOf', 'isoWeek') | ||||
|           this.formData.to_date = this.getPreDate('endOf', 'isoWeek') | ||||
|           break | ||||
|  | ||||
|         case 'Previous Month': | ||||
|           this.formData.from_date = this.getPreDate('startOf', 'month') | ||||
|           this.formData.to_date = this.getPreDate('endOf', 'month') | ||||
|           break | ||||
|  | ||||
|         case 'Previous Quarter': | ||||
|           this.formData.from_date = this.getPreDate('startOf', 'quarter') | ||||
|           this.formData.to_date = this.getPreDate('endOf', 'quarter') | ||||
|           break | ||||
|  | ||||
|         case 'Previous Year': | ||||
|           this.formData.from_date = this.getPreDate('startOf', 'year') | ||||
|           this.formData.to_date = this.getPreDate('endOf', 'year') | ||||
|           break | ||||
|  | ||||
|         default: | ||||
|           break | ||||
|       } | ||||
|     }, | ||||
|     setRangeToCustom () { | ||||
|       this.selectedRange = 'Custom' | ||||
|     }, | ||||
|     async getReports (isDownload = false) { | ||||
|       this.$v.range.$touch() | ||||
|       this.$v.formData.$touch() | ||||
|       if (this.$v.$invalid) { | ||||
|         return true | ||||
|       } | ||||
|  | ||||
|       this.url = `${this.siteURL}?from_date=${moment(this.formData.from_date).format('DD/MM/YYYY')}&to_date=${moment(this.formData.to_date).format('DD/MM/YYYY')}` | ||||
|       return true | ||||
|     }, | ||||
|     downloadReport () { | ||||
|       this.url += '&download=true' | ||||
|       setTimeout(() => { | ||||
|         this.url = `${this.siteURL}?from_date=${moment(this.formData.from_date).format('DD/MM/YYYY')}&to_date=${moment(this.formData.to_date).format('DD/MM/YYYY')}` | ||||
|       }, 200) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										248
									
								
								resources/assets/js/views/reports/SalesReports.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								resources/assets/js/views/reports/SalesReports.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,248 @@ | ||||
| <template> | ||||
|   <div class="row"> | ||||
|     <div class="col-md-4 reports-tab-container"> | ||||
|       <div class="row"> | ||||
|         <div class="col-md-8"> | ||||
|           <label class="report-label">{{ $t('reports.sales.date_range') }}</label> | ||||
|           <!-- <base-date-picker | ||||
|             v-model="range" | ||||
|             :invalid="$v.range.$error" | ||||
|             format="yyyy" | ||||
|             minimum-view="year" | ||||
|             @change="$v.range.$touch()" | ||||
|           /> --> | ||||
|           <base-select | ||||
|             v-model="selectedRange" | ||||
|             :options="dateRange" | ||||
|             :allow-empty="false" | ||||
|             :show-labels="false" | ||||
|             @input="onChangeDateRange" | ||||
|           /> | ||||
|           <span v-if="$v.range.$error && !$v.range.required" class="text-danger"> {{ $t('validation.required') }} </span> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row report-fields-container"> | ||||
|         <div class="col-md-6 report-field-container"> | ||||
|           <label class="report-label">{{ $t('reports.sales.from_date') }}</label> | ||||
|           <base-date-picker | ||||
|             v-model="formData.from_date" | ||||
|             :invalid="$v.formData.from_date.$error" | ||||
|             :calendar-button="true" | ||||
|             calendar-button-icon="calendar" | ||||
|             @change="$v.formData.from_date.$touch()" | ||||
|             @input="setRangeToCustom" | ||||
|           /> | ||||
|           <span v-if="$v.formData.from_date.$error && !$v.formData.from_date.required" class="text-danger"> {{ $t('validation.required') }} </span> | ||||
|         </div> | ||||
|         <div class="col-md-6 report-field-container"> | ||||
|           <label class="report-label">{{ $t('reports.sales.to_date') }}</label> | ||||
|           <base-date-picker | ||||
|             v-model="formData.to_date" | ||||
|             :invalid="$v.formData.to_date.$error" | ||||
|             :calendar-button="true" | ||||
|             calendar-button-icon="calendar" | ||||
|             @change="$v.formData.to_date.$touch()" | ||||
|             @input="setRangeToCustom" | ||||
|           /> | ||||
|           <span v-if="$v.formData.to_date.$error && !$v.formData.to_date.required" class="text-danger"> {{ $t('validation.required') }} </span> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row report-fields-container"> | ||||
|         <div class="col-md-8 report-field-container"> | ||||
|           <label class="report-label">{{ $t('reports.sales.report_type') }}</label> | ||||
|           <base-select | ||||
|             v-model="selectedType" | ||||
|             :options="reportTypes" | ||||
|             :allow-empty="false" | ||||
|             :show-labels="false" | ||||
|             :placeholder="$t('reports.sales.report_type')" | ||||
|             @input="getInitialReport" | ||||
|           /> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row report-submit-button-container"> | ||||
|         <div class="col-md-6"> | ||||
|           <base-button outline color="theme" class="report-button" @click="getReports()"> | ||||
|             {{ $t('reports.update_report') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="col-sm-8 reports-tab-container"> | ||||
|       <iframe :src="getReportUrl" class="reports-frame-style"/> | ||||
|       <a :href="getReportUrl" class="base-button btn btn-primary btn-lg report-view-button" target="_blank"> | ||||
|         <font-awesome-icon icon="file-pdf" class="vue-icon icon-left svg-inline--fa fa-download fa-w-16 mr-2" /> <span>{{ $t('reports.view_pdf') }}</span> | ||||
|       </a> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
| import moment from 'moment' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| const { required } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       reportTypes: ['By Customer', 'By Item'], | ||||
|       selectedType: 'By Customer', | ||||
|       dateRange: [ | ||||
|         'Today', | ||||
|         'This Week', | ||||
|         'This Month', | ||||
|         'This Quarter', | ||||
|         'This Year', | ||||
|         'Previous Week', | ||||
|         'Previous Month', | ||||
|         'Previous Quarter', | ||||
|         'Previous Year', | ||||
|         'Custom' | ||||
|       ], | ||||
|       selectedRange: 'This Month', | ||||
|       range: new Date(), | ||||
|       formData: { | ||||
|         from_date: moment().startOf('month').toString(), | ||||
|         to_date: moment().endOf('month').toString() | ||||
|       }, | ||||
|       url: null, | ||||
|       customerSiteURL: null, | ||||
|       itemsSiteURL: null | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     range: { | ||||
|       required | ||||
|     }, | ||||
|     formData: { | ||||
|       from_date: { | ||||
|         required | ||||
|       }, | ||||
|       to_date: { | ||||
|         required | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('company', [ | ||||
|       'getSelectedCompany' | ||||
|     ]), | ||||
|     getReportUrl () { | ||||
|       return this.url | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     range (newRange) { | ||||
|       this.formData.from_date = moment(newRange).startOf('year').toString() | ||||
|       this.formData.to_date = moment(newRange).endOf('year').toString() | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     this.customerSiteURL = `/reports/sales/customers/${this.getSelectedCompany.unique_hash}` | ||||
|     this.itemsSiteURL = `/reports/sales/items/${this.getSelectedCompany.unique_hash}` | ||||
|     this.getInitialReport() | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('salesReport', [ | ||||
|       'loadLinkByCustomer', | ||||
|       'loadLinkByItems' | ||||
|     ]), | ||||
|     getThisDate (type, time) { | ||||
|       return moment()[type](time).toString() | ||||
|     }, | ||||
|     getPreDate (type, time) { | ||||
|       return moment().subtract(1, time)[type](time).toString() | ||||
|     }, | ||||
|     onChangeDateRange () { | ||||
|       switch (this.selectedRange) { | ||||
|         case 'Today': | ||||
|           this.formData.from_date = moment().toString() | ||||
|           this.formData.to_date = moment().toString() | ||||
|           break | ||||
|  | ||||
|         case 'This Week': | ||||
|           this.formData.from_date = this.getThisDate('startOf', 'isoWeek') | ||||
|           this.formData.to_date = this.getThisDate('endOf', 'isoWeek') | ||||
|           break | ||||
|  | ||||
|         case 'This Month': | ||||
|           this.formData.from_date = this.getThisDate('startOf', 'month') | ||||
|           this.formData.to_date = this.getThisDate('endOf', 'month') | ||||
|           break | ||||
|  | ||||
|         case 'This Quarter': | ||||
|           this.formData.from_date = this.getThisDate('startOf', 'quarter') | ||||
|           this.formData.to_date = this.getThisDate('endOf', 'quarter') | ||||
|           break | ||||
|  | ||||
|         case 'This Year': | ||||
|           this.formData.from_date = this.getThisDate('startOf', 'year') | ||||
|           this.formData.to_date = this.getThisDate('endOf', 'year') | ||||
|           break | ||||
|  | ||||
|         case 'Previous Week': | ||||
|           this.formData.from_date = this.getPreDate('startOf', 'isoWeek') | ||||
|           this.formData.to_date = this.getPreDate('endOf', 'isoWeek') | ||||
|           break | ||||
|  | ||||
|         case 'Previous Month': | ||||
|           this.formData.from_date = this.getPreDate('startOf', 'month') | ||||
|           this.formData.to_date = this.getPreDate('endOf', 'month') | ||||
|           break | ||||
|  | ||||
|         case 'Previous Quarter': | ||||
|           this.formData.from_date = this.getPreDate('startOf', 'quarter') | ||||
|           this.formData.to_date = this.getPreDate('endOf', 'quarter') | ||||
|           break | ||||
|  | ||||
|         case 'Previous Year': | ||||
|           this.formData.from_date = this.getPreDate('startOf', 'year') | ||||
|           this.formData.to_date = this.getPreDate('endOf', 'year') | ||||
|           break | ||||
|  | ||||
|         default: | ||||
|           break | ||||
|       } | ||||
|     }, | ||||
|     setRangeToCustom () { | ||||
|       this.selectedRange = 'Custom' | ||||
|     }, | ||||
|     async getInitialReport () { | ||||
|       if (this.selectedType === 'By Customer') { | ||||
|         this.url = `${this.customerSiteURL}?from_date=${moment(this.formData.from_date).format('DD/MM/YYYY')}&to_date=${moment(this.formData.to_date).format('DD/MM/YYYY')}` | ||||
|         return true | ||||
|       } | ||||
|       this.url = `${this.itemsSiteURL}?from_date=${moment(this.formData.from_date).format('DD/MM/YYYY')}&to_date=${moment(this.formData.to_date).format('DD/MM/YYYY')}` | ||||
|       return true | ||||
|     }, | ||||
|     async getReports (isDownload = false) { | ||||
|       this.$v.range.$touch() | ||||
|       this.$v.formData.$touch() | ||||
|       if (this.$v.$invalid) { | ||||
|         return true | ||||
|       } | ||||
|  | ||||
|       if (this.selectedType === 'By Customer') { | ||||
|         this.url = `${this.customerSiteURL}?from_date=${moment(this.formData.from_date).format('DD/MM/YYYY')}&to_date=${moment(this.formData.to_date).format('DD/MM/YYYY')}` | ||||
|         return true | ||||
|       } | ||||
|  | ||||
|       this.url = `${this.itemsSiteURL}?from_date=${moment(this.formData.from_date).format('DD/MM/YYYY')}&to_date=${moment(this.formData.to_date).format('DD/MM/YYYY')}` | ||||
|       return true | ||||
|     }, | ||||
|     downloadReport () { | ||||
|       this.url += '&download=true' | ||||
|       setTimeout(() => { | ||||
|         if (this.selectedType === 'By Customer') { | ||||
|           this.url = `${this.customerSiteURL}?from_date=${moment(this.formData.from_date).format('DD/MM/YYYY')}&to_date=${moment(this.formData.to_date).format('DD/MM/YYYY')}` | ||||
|           return true | ||||
|         } | ||||
|         this.url = `${this.itemsSiteURL}?from_date=${moment(this.formData.from_date).format('DD/MM/YYYY')}&to_date=${moment(this.formData.to_date).format('DD/MM/YYYY')}` | ||||
|         return true | ||||
|       }, 200) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										201
									
								
								resources/assets/js/views/reports/TaxReport.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								resources/assets/js/views/reports/TaxReport.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,201 @@ | ||||
| <template> | ||||
|   <div class="row"> | ||||
|     <div class="col-md-4 reports-tab-container"> | ||||
|       <div class="row"> | ||||
|         <div class="col-md-8"> | ||||
|           <label class="report-label">{{ $t('reports.taxes.date_range') }}</label> | ||||
|           <base-select | ||||
|             v-model="selectedRange" | ||||
|             :options="dateRange" | ||||
|             :allow-empty="false" | ||||
|             :show-labels="false" | ||||
|             @input="onChangeDateRange" | ||||
|           /> | ||||
|           <span v-if="$v.range.$error && !$v.range.required" class="text-danger"> {{ $t('validation.required') }} </span> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row report-fields-container"> | ||||
|         <div class="col-md-6 report-field-container"> | ||||
|           <label class="report-label">{{ $t('reports.taxes.from_date') }}</label> | ||||
|           <base-date-picker | ||||
|             v-model="formData.from_date" | ||||
|             :invalid="$v.formData.from_date.$error" | ||||
|             :calendar-button="true" | ||||
|             calendar-button-icon="calendar" | ||||
|             @change="$v.formData.from_date.$touch()" | ||||
|           /> | ||||
|           <span v-if="$v.formData.from_date.$error && !$v.formData.from_date.required" class="text-danger"> {{ $t('validation.required') }} </span> | ||||
|         </div> | ||||
|         <div class="col-md-6 report-field-container"> | ||||
|           <label class="report-label">{{ $t('reports.taxes.to_date') }}</label> | ||||
|           <base-date-picker | ||||
|             v-model="formData.to_date" | ||||
|             :invalid="$v.formData.to_date.$error" | ||||
|             :calendar-button="true" | ||||
|             calendar-button-icon="calendar" | ||||
|             @change="$v.formData.to_date.$touch()" | ||||
|           /> | ||||
|           <span v-if="$v.formData.to_date.$error && !$v.formData.to_date.required" class="text-danger"> {{ $t('validation.required') }} </span> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row report-submit-button-container"> | ||||
|         <div class="col-md-6"> | ||||
|           <base-button outline color="theme" class="report-button" @click="getReports()"> | ||||
|             {{ $t('reports.update_report') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="col-sm-8 reports-tab-container"> | ||||
|       <iframe :src="getReportUrl" class="reports-frame-style"/> | ||||
|       <a :href="getReportUrl" class="base-button btn btn-primary btn-lg report-view-button" target="_blank"> | ||||
|         <font-awesome-icon icon="file-pdf" class="vue-icon icon-left svg-inline--fa fa-download fa-w-16 mr-2" /> <span>{{ $t('reports.view_pdf') }}</span> | ||||
|       </a> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapGetters } from 'vuex' | ||||
| import moment from 'moment' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| const { required } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       dateRange: [ | ||||
|         'Today', | ||||
|         'This Week', | ||||
|         'This Month', | ||||
|         'This Quarter', | ||||
|         'This Year', | ||||
|         'Previous Week', | ||||
|         'Previous Month', | ||||
|         'Previous Quarter', | ||||
|         'Previous Year', | ||||
|         'Custom' | ||||
|       ], | ||||
|       selectedRange: 'This Month', | ||||
|       range: new Date(), | ||||
|       formData: { | ||||
|         from_date: moment().startOf('month').toString(), | ||||
|         to_date: moment().endOf('month').toString() | ||||
|       }, | ||||
|       url: null, | ||||
|       siteURL: null | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     range: { | ||||
|       required | ||||
|     }, | ||||
|     formData: { | ||||
|       from_date: { | ||||
|         required | ||||
|       }, | ||||
|       to_date: { | ||||
|         required | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('company', [ | ||||
|       'getSelectedCompany' | ||||
|     ]), | ||||
|     getReportUrl () { | ||||
|       return this.url | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     range (newRange) { | ||||
|       this.formData.from_date = moment(newRange).startOf('year').toString() | ||||
|       this.formData.to_date = moment(newRange).endOf('year').toString() | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     this.siteURL = `/reports/tax-summary/${this.getSelectedCompany.unique_hash}` | ||||
|     this.url = `${this.siteURL}?from_date=${moment(this.formData.from_date).format('DD/MM/YYYY')}&to_date=${moment(this.formData.to_date).format('DD/MM/YYYY')}` | ||||
|   }, | ||||
|   methods: { | ||||
|     getThisDate (type, time) { | ||||
|       return moment()[type](time).toString() | ||||
|     }, | ||||
|     getPreDate (type, time) { | ||||
|       return moment().subtract(1, time)[type](time).toString() | ||||
|     }, | ||||
|     onChangeDateRange () { | ||||
|       switch (this.selectedRange) { | ||||
|         case 'Today': | ||||
|           this.formData.from_date = moment().toString() | ||||
|           this.formData.to_date = moment().toString() | ||||
|           break | ||||
|  | ||||
|         case 'This Week': | ||||
|           this.formData.from_date = this.getThisDate('startOf', 'isoWeek') | ||||
|           this.formData.to_date = this.getThisDate('endOf', 'isoWeek') | ||||
|           break | ||||
|  | ||||
|         case 'This Month': | ||||
|           this.formData.from_date = this.getThisDate('startOf', 'month') | ||||
|           this.formData.to_date = this.getThisDate('endOf', 'month') | ||||
|           break | ||||
|  | ||||
|         case 'This Quarter': | ||||
|           this.formData.from_date = this.getThisDate('startOf', 'quarter') | ||||
|           this.formData.to_date = this.getThisDate('endOf', 'quarter') | ||||
|           break | ||||
|  | ||||
|         case 'This Year': | ||||
|           this.formData.from_date = this.getThisDate('startOf', 'year') | ||||
|           this.formData.to_date = this.getThisDate('endOf', 'year') | ||||
|           break | ||||
|  | ||||
|         case 'Previous Week': | ||||
|           this.formData.from_date = this.getPreDate('startOf', 'isoWeek') | ||||
|           this.formData.to_date = this.getPreDate('endOf', 'isoWeek') | ||||
|           break | ||||
|  | ||||
|         case 'Previous Month': | ||||
|           this.formData.from_date = this.getPreDate('startOf', 'month') | ||||
|           this.formData.to_date = this.getPreDate('endOf', 'month') | ||||
|           break | ||||
|  | ||||
|         case 'Previous Quarter': | ||||
|           this.formData.from_date = this.getPreDate('startOf', 'quarter') | ||||
|           this.formData.to_date = this.getPreDate('endOf', 'quarter') | ||||
|           break | ||||
|  | ||||
|         case 'Previous Year': | ||||
|           this.formData.from_date = this.getPreDate('startOf', 'year') | ||||
|           this.formData.to_date = this.getPreDate('endOf', 'year') | ||||
|           break | ||||
|  | ||||
|         default: | ||||
|           break | ||||
|       } | ||||
|     }, | ||||
|     setRangeToCustom () { | ||||
|       this.selectedRange = 'Custom' | ||||
|     }, | ||||
|     async getReports (isDownload = false) { | ||||
|       this.$v.range.$touch() | ||||
|       this.$v.formData.$touch() | ||||
|  | ||||
|       if (this.$v.$invalid) { | ||||
|         return true | ||||
|       } | ||||
|  | ||||
|       this.url = `${this.siteURL}?from_date=${moment(this.formData.from_date).format('DD/MM/YYYY')}&to_date=${moment(this.formData.to_date).format('DD/MM/YYYY')}` | ||||
|       return true | ||||
|     }, | ||||
|     downloadReport () { | ||||
|       this.url += '&download=true' | ||||
|       setTimeout(() => { | ||||
|         this.url = `${this.siteURL}?from_date=${moment(this.formData.from_date).format('DD/MM/YYYY')}&to_date=${moment(this.formData.to_date).format('DD/MM/YYYY')}` | ||||
|       }, 200) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										87
									
								
								resources/assets/js/views/reports/layout/Index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								resources/assets/js/views/reports/layout/Index.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,87 @@ | ||||
| <template> | ||||
|   <div class="profit-loss-reports reports main-content"> | ||||
|     <div class="page-header"> | ||||
|       <h3 class="page-title"> {{ $tc('reports.report', 2) }}</h3> | ||||
|       <ol class="breadcrumb"> | ||||
|         <li class="breadcrumb-item"> | ||||
|           <router-link | ||||
|             slot="item-title" | ||||
|             to="dashboard"> | ||||
|             {{ $t('general.home') }} | ||||
|           </router-link> | ||||
|         </li> | ||||
|         <li class="breadcrumb-item"> | ||||
|           <router-link | ||||
|             slot="item-title" | ||||
|             to="/admin/reports/sales"> | ||||
|             {{ $tc('reports.report', 2) }} | ||||
|           </router-link> | ||||
|         </li> | ||||
|       </ol> | ||||
|       <div class="page-actions row"> | ||||
|         <div class="col-xs-2"> | ||||
|           <base-button icon="download" size="large" color="theme" @click="onDownload()"> | ||||
|             {{ $t('reports.download_pdf') }} | ||||
|           </base-button> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="row"> | ||||
|       <div class="col-sm-12"> | ||||
|         <!-- Tabs --> | ||||
|         <ul class="tabs"> | ||||
|           <li class="tab"> | ||||
|             <router-link class="tab-link" to="/admin/reports/sales">{{ $t('reports.sales.sales') }}</router-link> | ||||
|           </li> | ||||
|           <li class="tab"> | ||||
|             <router-link class="tab-link" to="/admin/reports/profit-loss">{{ $t('reports.profit_loss.profit_loss') }}</router-link> | ||||
|           </li> | ||||
|           <li class="tab"> | ||||
|             <router-link class="tab-link" to="/admin/reports/expenses">{{ $t('reports.expenses.expenses') }}</router-link> | ||||
|           </li> | ||||
|           <li class="tab"> | ||||
|             <router-link class="tab-link" to="/admin/reports/taxes">{{ $t('reports.taxes.taxes') }}</router-link> | ||||
|           </li> | ||||
|         </ul> | ||||
|       </div> | ||||
|     </div> | ||||
|     <transition | ||||
|       name="fade" | ||||
|       mode="out-in"> | ||||
|       <router-view ref="report"/> | ||||
|     </transition> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| export default { | ||||
|   watch: { | ||||
|     '$route.path' (newValue) { | ||||
|       if (newValue === '/admin/reports') { | ||||
|         this.$router.push('/admin/reports/sales') | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     if (this.$route.path === '/admin/reports') { | ||||
|       this.$router.push('/admin/reports/sales') | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     onDownload () { | ||||
|       this.$refs.report.downloadReport() | ||||
|     } | ||||
|   } | ||||
|  | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .tab { | ||||
|   padding: 0 !important; | ||||
| } | ||||
|  | ||||
| .tab-link { | ||||
|   padding: 10px 30px; | ||||
|   display: block | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										334
									
								
								resources/assets/js/views/settings/CompanyInfo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										334
									
								
								resources/assets/js/views/settings/CompanyInfo.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,334 @@ | ||||
| <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"> | ||||
|               <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: 'Cancle'}" | ||||
|             :cropper-options="cropperOptions" | ||||
|             :output-options="cropperOutputOptions" | ||||
|             :output-quality="0.8" | ||||
|             :upload-handler="cropperHandler" | ||||
|             trigger="#pick-avatar" | ||||
|             @changed="setFileObject" | ||||
|           /> | ||||
|         </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" | ||||
|               :invalid="$v.formData.phone.$error" | ||||
|               :placeholder="$t('settings.company_info.phone')" | ||||
|               @input="$v.formData.phone.$touch()" | ||||
|             /> | ||||
|             <div v-if="$v.formData.phone.$error"> | ||||
|               <span v-if="!$v.formData.phone.phone" class="text-danger">{{ $tc('validation.numbers_only') }}</span> | ||||
|             </div> | ||||
|           </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-select | ||||
|               v-model="state" | ||||
|               :options="states" | ||||
|               :searchable="true" | ||||
|               :disabled="isDisabledState" | ||||
|               :show-labels="false" | ||||
|               :placeholder="$t('general.select_state')" | ||||
|               label="name" | ||||
|               track-by="id" | ||||
|             /> | ||||
|           </div> | ||||
|           <div class="col-md-6 mb-4"> | ||||
|             <label class="input-label">{{ $tc('settings.company_info.city') }}</label> | ||||
|             <base-select | ||||
|               v-model="city" | ||||
|               :options="cities" | ||||
|               :searchable="true" | ||||
|               :show-labels="false" | ||||
|               :disabled="isDisabledCity" | ||||
|               :placeholder="$t('general.select_city')" | ||||
|               label="name" | ||||
|               track-by="id" | ||||
|             /> | ||||
|           </div> | ||||
|           <!-- <div class="col-md-6 mb-3"> | ||||
|             <label class="input-label">Website</label> | ||||
|             <base-input | ||||
|               v-model="formData.website" | ||||
|               placeholder="Website" | ||||
|             /> | ||||
|           </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')" | ||||
|               rows="2" | ||||
|             /> | ||||
|             <base-text-area | ||||
|               v-model="formData.address_street_2" | ||||
|               :placeholder="$tc('general.street_1')" | ||||
|               rows="2" | ||||
|             /> | ||||
|           </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, numeric } = 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: '', | ||||
|         logo: null, | ||||
|         email: '', | ||||
|         phone: null, | ||||
|         zip: null, | ||||
|         address_street_1: null, | ||||
|         address_street_2: null, | ||||
|         website: null, | ||||
|         country_id: null, | ||||
|         state_id: '', | ||||
|         city_id: '' | ||||
|       }, | ||||
|       isLoading: false, | ||||
|       isHidden: false, | ||||
|       country: null, | ||||
|       previewLogo: null, | ||||
|       city: null, | ||||
|       state: null, | ||||
|       countries: [], | ||||
|       isDisabledState: true, | ||||
|       isDisabledCity: true, | ||||
|       states: [], | ||||
|       cities: [], | ||||
|       passData: [], | ||||
|       fileSendUrl: '/api/settings/company', | ||||
|       fileObject: null | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     country (newCountry) { | ||||
|       this.formData.country_id = newCountry.id | ||||
|       if (this.formData.country_id) { | ||||
|         this.isDisabledState = false | ||||
|       } | ||||
|       this.fetchState() | ||||
|       if (this.isFetchingData) { | ||||
|         return true | ||||
|       } | ||||
|  | ||||
|       this.state = null | ||||
|       this.city = null | ||||
|     }, | ||||
|     state (newState) { | ||||
|       if (newState !== null && newState !== undefined) { | ||||
|         this.formData.state_id = newState.id | ||||
|         this.fetchCities() | ||||
|         this.isDisabledCity = false | ||||
|  | ||||
|         if (this.isFetchingData) { | ||||
|           this.isFetchingData = false | ||||
|           return true | ||||
|         } | ||||
|         this.city = null | ||||
|         return true | ||||
|       } | ||||
|       // this.formData.state_id = null | ||||
|       this.cities = [] | ||||
|       this.city = null | ||||
|       // this.formData.city_id = null | ||||
|       this.isDisabledCity = true | ||||
|       return true | ||||
|     }, | ||||
|     city (newCity) { | ||||
|       if (newCity !== null && newCity !== undefined) { | ||||
|         this.formData.city_id = newCity.id | ||||
|         return true | ||||
|       } | ||||
|       // this.formData.city_id = null | ||||
|       // return true | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     formData: { | ||||
|       name: { | ||||
|         required | ||||
|       }, | ||||
|       country_id: { | ||||
|         required | ||||
|       }, | ||||
|       email: { | ||||
|         email | ||||
|       }, | ||||
|       phone: { | ||||
|         numeric | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   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 | ||||
|     }, | ||||
|     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.country = response.data.user.addresses[0].country | ||||
|       this.state = response.data.user.addresses[0].state | ||||
|       this.city = response.data.user.addresses[0].city | ||||
|       this.previewLogo = response.data.user.company.logo | ||||
|     }, | ||||
|     async updateCompany () { | ||||
|       this.$v.formData.$touch() | ||||
|       if (this.$v.$invalid) { | ||||
|         return true | ||||
|       } | ||||
|       this.isLoading = true | ||||
|       let data = new FormData() | ||||
|       data.append('name', this.formData.name) | ||||
|       data.append('address_street_1', this.formData.address_street_1) | ||||
|       data.append('address_street_2', this.formData.address_street_2) | ||||
|       data.append('city_id', this.formData.city_id) | ||||
|       data.append('state_id', this.formData.state_id) | ||||
|       data.append('country_id', this.formData.country_id) | ||||
|       data.append('zip', this.formData.zip) | ||||
|       data.append('phone', this.formData.phone) | ||||
|       if (this.fileObject) { | ||||
|         data.append('logo', this.fileObject) | ||||
|       } | ||||
|       let response = await this.editCompany(data) | ||||
|       if (response.data.success) { | ||||
|         this.isLoading = false | ||||
|         window.toastr['success'](this.$t('settings.company_info.updated_message')) | ||||
|         return true | ||||
|       } | ||||
|       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 | ||||
|       } | ||||
|     }, | ||||
|     async fetchState () { | ||||
|       this.$v.formData.country_id.$touch() | ||||
|       let res = await window.axios.get(`/api/states/${this.country.id}`) | ||||
|       if (res) { | ||||
|         this.states = res.data.states | ||||
|       } | ||||
|     }, | ||||
|     async fetchCities () { | ||||
|       let res = await window.axios.get(`/api/cities/${this.state.id}`) | ||||
|       if (res) { | ||||
|         this.cities = res.data.cities | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										131
									
								
								resources/assets/js/views/settings/ExpenseCategory.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								resources/assets/js/views/settings/ExpenseCategory.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,131 @@ | ||||
| <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) { | ||||
|       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['success'](this.$t('settings.expense_category.already_in_use')) | ||||
|     }, | ||||
|     openCategoryModal () { | ||||
|       this.openModal({ | ||||
|         'title': 'Add Category', | ||||
|         'componentName': 'CategoryModal' | ||||
|       }) | ||||
|       this.$refs.table.refresh() | ||||
|     }, | ||||
|     async EditCategory (id) { | ||||
|       let response = await this.fetchCategory(id) | ||||
|       this.openModal({ | ||||
|         'title': 'Edit Category', | ||||
|         'componentName': 'CategoryModal', | ||||
|         'id': id, | ||||
|         'data': response.data.category | ||||
|       }) | ||||
|       this.$refs.table.refresh() | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										102
									
								
								resources/assets/js/views/settings/GeneralSetting.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								resources/assets/js/views/settings/GeneralSetting.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,102 @@ | ||||
| <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> | ||||
							
								
								
									
										153
									
								
								resources/assets/js/views/settings/Notifications.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								resources/assets/js/views/settings/Notifications.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,153 @@ | ||||
| <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> | ||||
							
								
								
									
										74
									
								
								resources/assets/js/views/settings/PDFSetting.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								resources/assets/js/views/settings/PDFSetting.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,74 @@ | ||||
| <template> | ||||
|   <div class="main-content pdfsetting"> | ||||
|     <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.pdf.title') }}</router-link></li> | ||||
|       </ol> | ||||
|     </div> | ||||
|     <div class="row"> | ||||
|       <div class="col-sm-12"> | ||||
|         <div class="card"> | ||||
|           <div class="card-header"> | ||||
|             <div class="caption"> | ||||
|               <h6>{{ $t('settings.pdf.title') }}</h6> | ||||
|             </div> | ||||
|             <div class="actions"> | ||||
|               <base-button color="theme" size="small" @click="submitData"> | ||||
|                 {{ $t('general.update') }} | ||||
|               </base-button> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="card-body"> | ||||
|             <div class="row"> | ||||
|               <label class="col-md-2 form-control-label">{{ $t('settings.pdf.footer_text') }} : </label> | ||||
|               <div class="col-md-12"> | ||||
|                 <input v-model="footerText" type="text" class="form-control"> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="row pdfsetting__img-row"> | ||||
|               <label class="col-md-2 form-control-label">{{ $t('settings.pdf.pdf_layout') }} : </label> | ||||
|               <div class="col-md-12"> | ||||
|                 <image-radio :current-p-d-f="pdfSet" @selectedPDF="selectedPDF"/> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import ImageRadio from '../components/ImageRadio.vue' | ||||
| import { mapActions, mapMutations } from 'vuex' | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     'image-radio': ImageRadio | ||||
|   }, | ||||
|   data () { | ||||
|     return this.$store.state.pdf_setting | ||||
|     // return { | ||||
|     //   pdfSet: '1', | ||||
|     //   footerText: null | ||||
|     // } | ||||
|   }, | ||||
|   mounted () { | ||||
|     this.loadData() | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('pdf_setting', [ | ||||
|       'loadData', | ||||
|       'submitData' | ||||
|     ]), | ||||
|     // async submitData () { | ||||
|  | ||||
|     // }, | ||||
|     ...mapMutations('pdf_setting', [ | ||||
|       'selectedPDF' | ||||
|     ]) | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										243
									
								
								resources/assets/js/views/settings/Preferences.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								resources/assets/js/views/settings/Preferences.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,243 @@ | ||||
| <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" | ||||
|               :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: { | ||||
|     ...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> | ||||
							
								
								
									
										189
									
								
								resources/assets/js/views/settings/TaxTypes.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								resources/assets/js/views/settings/TaxTypes.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,189 @@ | ||||
| <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" | ||||
|           :filterable="true" | ||||
|           :label="$t('settings.tax_types.tax_name')" | ||||
|         > | ||||
|           <template slot-scope="row"> | ||||
|             <span>{{ $t('settings.tax_types.tax_name') }}</span> | ||||
|             <span class="tax-name"> | ||||
|               {{ row.name }} | ||||
|             </span> | ||||
|           </template> | ||||
|         </table-column> | ||||
|         <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 : | ||||
|           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) { | ||||
|       let response = await this.deleteTaxType(id) | ||||
|       if (response.data.success) { | ||||
|         window.toastr['success'](this.$t('settings.sales_taxes.deleted_message')) | ||||
|         this.id = null | ||||
|         this.$refs.table.refresh() | ||||
|         return true | ||||
|       }window.toastr['success'](this.$t('settings.sales_taxes.already_in_use')) | ||||
|     }, | ||||
|     openTaxModal () { | ||||
|       this.openModal({ | ||||
|         'title': 'Add Tax', | ||||
|         'componentName': 'TaxTypeModal' | ||||
|       }) | ||||
|       this.$refs.table.refresh() | ||||
|     }, | ||||
|     async EditTax (id) { | ||||
|       let response = await this.fetchTaxType(id) | ||||
|       this.openModal({ | ||||
|         'title': 'Edit Tax', | ||||
|         'componentName': 'TaxTypeModal', | ||||
|         'id': id, | ||||
|         'data': response.data.taxType | ||||
|       }) | ||||
|       this.$refs.table.refresh() | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										158
									
								
								resources/assets/js/views/settings/UserProfile.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								resources/assets/js/views/settings/UserProfile.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,158 @@ | ||||
| <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"> | ||||
|           <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> | ||||
|           <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' | ||||
| const { required, requiredIf, sameAs, email } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       isLoading: false, | ||||
|       formData: { | ||||
|         name: null, | ||||
|         email: null, | ||||
|         password: null, | ||||
|         confirm_password: null | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     formData: { | ||||
|       name: { | ||||
|         required | ||||
|       }, | ||||
|       email: { | ||||
|         required, | ||||
|         email | ||||
|       }, | ||||
|       password: { | ||||
|       }, | ||||
|       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' | ||||
|     ]), | ||||
|     async setInitialData () { | ||||
|       let response = await this.loadData() | ||||
|       this.formData.name = response.data.name | ||||
|       this.formData.email = response.data.email | ||||
|     }, | ||||
|     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 | ||||
|         window.toastr['success'](this.$t('settings.account_settings.updated_message')) | ||||
|         return true | ||||
|       } | ||||
|       window.toastr['error'](response.data.error) | ||||
|       return true | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										126
									
								
								resources/assets/js/views/settings/currency/Index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								resources/assets/js/views/settings/currency/Index.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,126 @@ | ||||
| <template> | ||||
|   <div class="main-content"> | ||||
|     <div class="page-header"> | ||||
|       <h3 class="page-title">{{ $tc('navigation.currency', 2) }}</h3> | ||||
|       <ol class="breadcrumb"> | ||||
|         <li class="breadcrumb-item"> | ||||
|           <router-link | ||||
|             slot="item-title" | ||||
|             to="/admin/dashboard"> | ||||
|             {{ $t('navigation.home') }} | ||||
|           </router-link> | ||||
|         </li> | ||||
|         <li class="breadcrumb-item"> | ||||
|           <router-link | ||||
|             slot="item-title" | ||||
|             to="#"> | ||||
|             {{ $tc('navigation.currency', 2) }} | ||||
|           </router-link> | ||||
|         </li> | ||||
|       </ol> | ||||
|     </div> | ||||
|     <div class="row"> | ||||
|       <div class="col-sm-6"> | ||||
|         <div class="card"> | ||||
|           <div class="card-header"> | ||||
|             <div class="caption"> | ||||
|               <h6>{{ $t('settings.currencies.select_currency') }}:</h6> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="card-body"> | ||||
|             <div class="form-group"> | ||||
|               <select | ||||
|                 v-model.trim="currencyId" | ||||
|                 class="form-control" | ||||
|                 @change="selectCurrency()" | ||||
|               > | ||||
|                 <option | ||||
|                   v-for="(currency, index) in currencies" | ||||
|                   :key="index" | ||||
|                   :value="currency.id" | ||||
|                 > | ||||
|                   {{ currency.name }} | ||||
|                 </option> | ||||
|               </select> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="row"> | ||||
|       <div class="col-sm-12"> | ||||
|         <div class="card"> | ||||
|           <div class="card-header"> | ||||
|             <div class="caption"> | ||||
|               <h6>{{ $t('settings.currencies.currencies_list') }}</h6> | ||||
|             </div> | ||||
|             <div class="actions"> | ||||
|               <router-link slot="item-title" to="currencies/create"> | ||||
|                 <base-button icon="plus" color="theme" size="small"> | ||||
|                   {{ $t('navigation.add') }} {{ $t('navigation.new') }} | ||||
|                 </base-button> | ||||
|               </router-link> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="card-body"> | ||||
|             <table-component | ||||
|               ref="table" | ||||
|               :data="currencies" | ||||
|               table-class="table" | ||||
|               sort-by="name" | ||||
|               sort-order="asc" | ||||
|             > | ||||
|               <table-column :label="$t('settings.currencies.name')" show="name" /> | ||||
|               <table-column :label="$t('settings.currencies.code')" show="code" /> | ||||
|               <table-column :label="$t('settings.currencies.symbol')" show="symbol" /> | ||||
|               <table-column :label="$t('settings.currencies.precision')" show="precision" /> | ||||
|               <table-column :label="$t('settings.currencies.thousand_separator')" show="thousand_separator" /> | ||||
|               <table-column :label="$t('settings.currencies.decimal_separator')" show="decimal_separator" /> | ||||
|               <table-column | ||||
|                 :sortable="false" | ||||
|                 :filterable="false" | ||||
|                 :label="$t('settings.currencies.position')" | ||||
|               > | ||||
|                 <template slot-scope="row"> | ||||
|                   <span v-if="row.swap_currency_symbol === 0">{{ $t('settings.currencies.right') }}</span> | ||||
|                   <span v-if="row.swap_currency_symbol === 1">{{ $t('settings.currencies.left') }}</span> | ||||
|                 </template> | ||||
|               </table-column> | ||||
|               <table-column | ||||
|                 :sortable="false" | ||||
|                 :filterable="false" | ||||
|                 :label="$t('settings.currencies.action')" | ||||
|               > | ||||
|                 <template slot-scope="row"> | ||||
|                   <div class="table__actions"> | ||||
|                     <router-link slot="item-title" :to="{path: `currencies/${row.id}/edit`}">{{ $t('navigation.edit') }}</router-link> | ||||
|                     <div class="table__item--cursor-pointer" @click="removeItems(row.id)">{{ $t('navigation.delete') }}</div> | ||||
|                   </div> | ||||
|                 </template> | ||||
|               </table-column> | ||||
|             </table-component> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import { mapActions } from 'vuex' | ||||
|  | ||||
| export default { | ||||
|   data () { | ||||
|     return this.$store.state.currency | ||||
|   }, | ||||
|   mounted () { | ||||
|     this.indexLoadData() | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('currency', [ | ||||
|       'indexLoadData', | ||||
|       'removeItems', | ||||
|       'selectCurrency' | ||||
|     ]) | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										185
									
								
								resources/assets/js/views/settings/currency/currency.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								resources/assets/js/views/settings/currency/currency.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,185 @@ | ||||
| <template> | ||||
|   <div class="main-content currencycreate"> | ||||
|     <div class="page-header"> | ||||
|       <h3 class="page-title">{{ $t('settings.currencies.add_currency') }}</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/currencies">{{ $tc('settings.currencies.currency',2) }}</router-link></li> | ||||
|         <li class="breadcrumb-item"><a href="#">{{ $t('navigation.add') }}</a></li> | ||||
|       </ol> | ||||
|       <div class="page-actions"> | ||||
|         <router-link slot="item-title" to="/admin/settings/currencies"> | ||||
|           <base-button icon="backward" color="theme"> | ||||
|             {{ $t('navigation.go_back') }} | ||||
|           </base-button> | ||||
|         </router-link> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="row"> | ||||
|       <div class="col-sm-6"> | ||||
|         <div class="card"> | ||||
|           <form action="" @submit.prevent="submiteCurrency"> | ||||
|             <div class="card-body"> | ||||
|               <div class="form-group"> | ||||
|                 <label class="control-label">{{ $t('settings.currencies.name') }}:</label><span class="required text-danger"> *</span> | ||||
|                 <input | ||||
|                   :class="{ error: $v.formData.name.$error }" | ||||
|                   v-model.trim="formData.name" | ||||
|                   type="text" | ||||
|                   name="name" | ||||
|                   class="form-control" | ||||
|                   @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="form-group"> | ||||
|                 <label class="control-label">{{ $t('settings.currencies.code') }}:</label><span class="required"> *</span> | ||||
|                 <input | ||||
|                   :class="{ error: $v.formData.code.$error }" | ||||
|                   v-model="formData.code" | ||||
|                   type="text" | ||||
|                   name="code" | ||||
|                   class="form-control" | ||||
|                   @input="$v.formData.code.$touch()" | ||||
|                 > | ||||
|                 <div v-if="$v.formData.code.$error"> | ||||
|                   <span v-if="!$v.formData.code.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="control-label">{{ $t('settings.currencies.symbol') }}:</label> | ||||
|                 <input | ||||
|                   v-model="formData.symbol" | ||||
|                   type="text" | ||||
|                   name="symbol" | ||||
|                   class="form-control" | ||||
|                 > | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="control-label">{{ $t('settings.currencies.precision') }}:</label> | ||||
|                 <input | ||||
|                   v-model="formData.precision" | ||||
|                   type="text" | ||||
|                   name="precision" | ||||
|                   class="form-control" | ||||
|                 > | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="control-label">{{ $t('settings.currencies.thousand_separator') }}:</label><span class="required"> *</span> | ||||
|                 <input | ||||
|                   :class="{ error: $v.formData.thousand_separator.$error }" | ||||
|                   v-model="formData.thousand_separator" | ||||
|                   type="text" | ||||
|                   name="thousand_separator" | ||||
|                   class="form-control" | ||||
|                   @input="$v.formData.thousand_separator.$touch()" | ||||
|                 > | ||||
|                 <div v-if="$v.formData.thousand_separator.$error"> | ||||
|                   <span v-if="!$v.formData.thousand_separator.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label class="control-label">{{ $t('settings.currencies.decimal_separator') }}:</label><span class="required"> *</span> | ||||
|                 <input | ||||
|                   :class="{ error: $v.formData.decimal_separator.$error }" | ||||
|                   v-model="formData.decimal_separator" | ||||
|                   type="text" | ||||
|                   name="decimal_separator" | ||||
|                   class="form-control" | ||||
|                   @input="$v.formData.decimal_separator.$touch()" | ||||
|                 > | ||||
|                 <div v-if="$v.formData.decimal_separator.$error"> | ||||
|                   <span v-if="!$v.formData.decimal_separator.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
|                 <label>{{ $t('settings.currencies.position_of_symbol') }}:</label><span class="required"> *</span> | ||||
|                 <select | ||||
|                   v-model="formData.swap_currency_symbol" | ||||
|                   :class="{ error: $v.formData.swap_currency_symbol.$error }" | ||||
|                   class="form-control ls-select2" | ||||
|                   name="swap_currency_symbol" | ||||
|                   @select="$v.formData.swap_currency_symbol.$touch()" | ||||
|                 > | ||||
|                   <option value="0">{{ $t('settings.currencies.right') }}</option> | ||||
|                   <option value="1">{{ $t('settings.currencies.left') }}</option> | ||||
|                 </select> | ||||
|                 <div v-if="$v.formData.swap_currency_symbol.$error"> | ||||
|                   <span v-if="!$v.formData.swap_currency_symbol.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <base-button color="theme" type="submit"> | ||||
|                 {{ $t('navigation.add') }} | ||||
|               </base-button> | ||||
|             </div> | ||||
|           </form> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import { mapActions } from 'vuex' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| const { required } = require('vuelidate/lib/validators') | ||||
| export default { | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return this.$store.state.currency | ||||
|   }, | ||||
|   computed: { | ||||
|     isEdit () { | ||||
|       if (this.$route.name === 'currencyedit') { | ||||
|         return true | ||||
|       } | ||||
|       return false | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     formData: { | ||||
|       name: { | ||||
|         required | ||||
|       }, | ||||
|       code: { | ||||
|         required | ||||
|       }, | ||||
|       thousand_separator: { | ||||
|         required | ||||
|       }, | ||||
|       decimal_separator: { | ||||
|         required | ||||
|       }, | ||||
|       swap_currency_symbol: { | ||||
|         required | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     if (!this.isEdit) { | ||||
|       return true | ||||
|     } | ||||
|     this.loadData(this.$route.params.id) | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('currency', [ | ||||
|       'loadData', | ||||
|       'addCurrency', | ||||
|       'editCurrency' | ||||
|     ]), | ||||
|     async submiteCurrency () { | ||||
|       this.$v.formData.$touch() | ||||
|       if (this.$v.$invalid) { | ||||
|         return false | ||||
|       } | ||||
|       if (this.isEdit) { | ||||
|         this.editCurrency(this.$route.params.id) | ||||
|         return true | ||||
|       } | ||||
|       this.addCurrency() | ||||
|       return true | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										93
									
								
								resources/assets/js/views/settings/layout/Index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								resources/assets/js/views/settings/layout/Index.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | ||||
| <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/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/notifications', | ||||
|           title: 'settings.menu_title.notifications', | ||||
|           icon: 'bell', | ||||
|           iconType: 'far' | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   }, | ||||
|   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> | ||||
							
								
								
									
										311
									
								
								resources/assets/js/views/wizard/CompanyInfo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								resources/assets/js/views/wizard/CompanyInfo.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,311 @@ | ||||
| <template> | ||||
|   <div class="card-body"> | ||||
|     <form action="" @submit.prevent="next()"> | ||||
|       <!-- <div v-if="previewLogo" class="upload-logo"> | ||||
|         <label class="form-label">{{ $t('wizard.logo_preview') }}</label><br> | ||||
|         <img v-if="previewLogo" :src="previewLogo" class="preview-logo"> | ||||
|       </div> --> | ||||
|       <p class="form-title">{{ $t('wizard.company_info') }}</p> | ||||
|       <p class="form-desc">{{ $t('wizard.company_info_desc') }}</p> | ||||
|       <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"> | ||||
|             <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"> {{ $t('general.choose_file') }} </p> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|         <avatar-cropper | ||||
|           :labels="{ submit: 'submit', cancel: 'Cancle'}" | ||||
|           :cropper-options="cropperOptions" | ||||
|           :output-options="cropperOutputOptions" | ||||
|           :output-quality="0.8" | ||||
|           :upload-handler="cropperHandler" | ||||
|           trigger="#pick-avatar" | ||||
|           @changed="setFileObject" | ||||
|         /> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.company_name') }}</label><span class="text-danger"> *</span> | ||||
|           <base-input | ||||
|             :invalid="$v.companyData.name.$error" | ||||
|             v-model.trim="companyData.name" | ||||
|             type="text" | ||||
|             name="name" | ||||
|             @input="$v.companyData.name.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.companyData.name.$error"> | ||||
|             <span v-if="!$v.companyData.name.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.country') }}</label><span class="text-danger"> *</span> | ||||
|           <base-select | ||||
|             v-model="country" | ||||
|             :class="{'error': $v.companyData.country_id.$error }" | ||||
|             :options="countries" | ||||
|             :searchable="true" | ||||
|             :allow-empty="false" | ||||
|             :show-labels="false" | ||||
|             :placeholder="$t('general.select_country')" | ||||
|             track-by="id" | ||||
|             label="name" | ||||
|             @input="fetchState()" | ||||
|           /> | ||||
|           <div v-if="$v.companyData.country_id.$error"> | ||||
|             <span v-if="!$v.companyData.country_id.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.state') }}</label> | ||||
|           <base-select | ||||
|             v-model="state" | ||||
|             :options="states" | ||||
|             :searchable="true" | ||||
|             :show-labels="false" | ||||
|             :disabled="isDisabledState" | ||||
|             :placeholder="$t('general.select_state')" | ||||
|             track-by="id" | ||||
|             label="name" | ||||
|             @input="fetchCities" | ||||
|           /> | ||||
|         </div> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.city') }}</label> | ||||
|           <base-select | ||||
|             v-model="city" | ||||
|             :options="cities" | ||||
|             :searchable="true" | ||||
|             :show-labels="false" | ||||
|             :disabled="isDisabledCity" | ||||
|             :placeholder="$t('general.select_city')" | ||||
|             track-by="id" | ||||
|             label="name" | ||||
|           /> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.address') }}</label> | ||||
|           <base-text-area | ||||
|             v-model.trim="companyData.address_street_1" | ||||
|             :placeholder="$t('general.street_1')" | ||||
|             name="billing_street1" | ||||
|             rows="2" | ||||
|           /> | ||||
|           <base-text-area | ||||
|             v-model="companyData.address_street_2" | ||||
|             :placeholder="$t('general.street_2')" | ||||
|             name="billing_street2" | ||||
|             rows="2" | ||||
|           /> | ||||
|         </div> | ||||
|         <div class="col-md-6"> | ||||
|           <div class="row"> | ||||
|             <div class="col-md-12"> | ||||
|               <label class="form-label">{{ $t('wizard.zip_code') }}</label> | ||||
|               <base-input | ||||
|                 v-model.trim="companyData.zip" | ||||
|                 type="text" | ||||
|                 name="zip" | ||||
|               /> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="row"> | ||||
|             <div class="col-md-12"> | ||||
|               <label class="form-label">{{ $t('wizard.phone') }}</label> | ||||
|               <base-input | ||||
|                 v-model.trim="companyData.phone" | ||||
|                 type="text" | ||||
|                 name="phone" | ||||
|               /> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <base-button | ||||
|         :loading="loading" | ||||
|         class="pull-right" | ||||
|         icon="save" | ||||
|         color="theme" | ||||
|         type="submit" | ||||
|       > | ||||
|         {{ $t('wizard.save_cont') }} | ||||
|       </base-button> | ||||
|     </form> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import MultiSelect from 'vue-multiselect' | ||||
| import AvatarCropper from 'vue-avatar-cropper' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import Ls from '../../services/ls' | ||||
| const { required, minLength, email } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     MultiSelect, | ||||
|     AvatarCropper | ||||
|   }, | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       cropperOutputOptions: { | ||||
|         width: 150, | ||||
|         height: 150 | ||||
|       }, | ||||
|       cropperOptions: { | ||||
|         autoCropArea: 1, | ||||
|         viewMode: 0, | ||||
|         movable: true, | ||||
|         zoomable: true | ||||
|       }, | ||||
|       companyData: { | ||||
|         logo: '', | ||||
|         name: null, | ||||
|         address_street_1: '', | ||||
|         address_street_2: '', | ||||
|         city_id: '', | ||||
|         state_id: '', | ||||
|         country_id: '', | ||||
|         zip: '', | ||||
|         phone: '' | ||||
|       }, | ||||
|       loading: false, | ||||
|       step: 1, | ||||
|       countries: [], | ||||
|       country: null, | ||||
|       states: [], | ||||
|       state: null, | ||||
|       cities: [], | ||||
|       city: null, | ||||
|       previewLogo: null, | ||||
|       isDisabledCity: true, | ||||
|       isDisabledState: true | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     companyData: { | ||||
|       name: { | ||||
|         required | ||||
|       }, | ||||
|       country_id: { | ||||
|         required | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     country ({ id }) { | ||||
|       this.companyData.country_id = id | ||||
|       this.state = null | ||||
|       this.city = null | ||||
|       if (id !== null && id !== undefined) { | ||||
|         this.isDisabledState = false | ||||
|         return true | ||||
|       } | ||||
|       this.isDisabledState = true | ||||
|       return true | ||||
|     }, | ||||
|     state (newState) { | ||||
|       if (newState !== null && newState !== undefined) { | ||||
|         this.city = null | ||||
|         this.companyData.state_id = newState.id | ||||
|         this.isDisabledCity = false | ||||
|         return true | ||||
|       } | ||||
|       this.companyData.state_id = null | ||||
|       this.isDisabledCity = true | ||||
|       this.cities = [] | ||||
|       this.city = null | ||||
|       this.companyData.city_id = null | ||||
|       return true | ||||
|     }, | ||||
|     city (newCity) { | ||||
|       if (newCity !== null && newCity !== undefined) { | ||||
|         this.companyData.city_id = newCity.id | ||||
|         return true | ||||
|       } | ||||
|       this.companyData.city_id = null | ||||
|       return true | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     this.fetchCountry() | ||||
|   }, | ||||
|   methods: { | ||||
|     cropperHandler (cropper) { | ||||
|       this.previewLogo = cropper.getCroppedCanvas().toDataURL(this.cropperOutputMime) | ||||
|     }, | ||||
|     setFileObject (file) { | ||||
|       this.fileObject = file | ||||
|     }, | ||||
|     async next () { | ||||
|       this.$v.companyData.$touch() | ||||
|       if (this.$v.companyData.$invalid) { | ||||
|         return true | ||||
|       } | ||||
|       this.loading = true | ||||
|       let data = new FormData() | ||||
|       data.append('logo', this.fileObject) | ||||
|       data.append('name', this.companyData.name) | ||||
|       data.append('address_street_1', this.companyData.address_street_1) | ||||
|       data.append('address_street_2', this.companyData.address_street_2) | ||||
|       data.append('city_id', this.companyData.city_id) | ||||
|       data.append('state_id', this.companyData.state_id) | ||||
|       data.append('country_id', this.companyData.country_id) | ||||
|       data.append('zip', this.companyData.zip) | ||||
|       data.append('phone', this.companyData.phone) | ||||
|  | ||||
|       let response = await window.axios.post('/api/admin/onboarding/company', data, { | ||||
|         headers: { | ||||
|           'Content-Type': 'multipart/form-data' | ||||
|         } | ||||
|       }) | ||||
|  | ||||
|       if (response.data) { | ||||
|         this.$emit('next') | ||||
|         this.loading = false | ||||
|       } | ||||
|     }, | ||||
|     onFileChange (e) { | ||||
|       var input = event.target | ||||
|       this.companyData.logo = input.files[0] | ||||
|       if (input.files && input.files[0]) { | ||||
|         var reader = new FileReader() | ||||
|         reader.onload = (e) => { | ||||
|           this.previewLogo = e.target.result | ||||
|         } | ||||
|         reader.readAsDataURL(input.files[0]) | ||||
|       } | ||||
|     }, | ||||
|     async fetchCountry () { | ||||
|       let res = await window.axios.get('/api/countries') | ||||
|       if (res) { | ||||
|         this.countries = res.data.countries | ||||
|       } | ||||
|     }, | ||||
|     async fetchState () { | ||||
|       this.$v.companyData.country_id.$touch() | ||||
|       let res = await window.axios.get(`/api/states/${this.country.id}`) | ||||
|       if (res) { | ||||
|         this.states = res.data.states | ||||
|       } | ||||
|     }, | ||||
|     async fetchCities () { | ||||
|       if (this.state === null || this.state === undefined) { | ||||
|         return false | ||||
|       } | ||||
|       let res = await window.axios.get(`/api/cities/${this.state.id}`) | ||||
|       if (res) { | ||||
|         this.cities = res.data.cities | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										216
									
								
								resources/assets/js/views/wizard/Database.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								resources/assets/js/views/wizard/Database.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,216 @@ | ||||
| <template> | ||||
|   <div class="card-body"> | ||||
|     <form action="" @submit.prevent="next()"> | ||||
|       <p class="form-title">{{ $t('wizard.database.database') }}</p> | ||||
|       <p class="form-desc">{{ $t('wizard.database.desc') }}</p> | ||||
|       <div class="row mt-5"> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.database.app_url') }}</label> | ||||
|           <span class="text-danger"> * </span> | ||||
|           <base-input | ||||
|             :invalid="$v.databaseData.app_url.$error" | ||||
|             v-model.trim="databaseData.app_url" | ||||
|             type="text" | ||||
|             name="name" | ||||
|             @input="$v.databaseData.app_url.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.databaseData.app_url.$error"> | ||||
|             <span v-if="!$v.databaseData.app_url.required" class="text-danger"> | ||||
|               {{ $tc('validation.required') }} | ||||
|             </span> | ||||
|             <span v-if="!$v.databaseData.app_url.url" class="text-danger"> | ||||
|               {{ $tc('validation.invalid_url') }} | ||||
|             </span> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.database.connection') }}</label> | ||||
|           <span class="text-danger"> *</span> | ||||
|           <base-select | ||||
|             v-model="databaseData.database_connection" | ||||
|             :invalid="$v.databaseData.database_connection.$error" | ||||
|             :options="connections" | ||||
|             :searchable="true" | ||||
|             :show-labels="false" | ||||
|             @change="$v.databaseData.database_connection.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.databaseData.database_connection.$error"> | ||||
|             <span v-if="!$v.databaseData.database_connection.required" class="text-danger"> | ||||
|               {{ $tc('validation.required') }} | ||||
|             </span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.database.port') }}</label> | ||||
|           <span class="text-danger"> *</span> | ||||
|           <base-input | ||||
|             :invalid="$v.databaseData.database_port.$error" | ||||
|             v-model.trim="databaseData.database_port" | ||||
|             type="text" | ||||
|             name="database_port" | ||||
|             @input="$v.databaseData.database_port.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.databaseData.database_port.$error"> | ||||
|             <span v-if="!$v.databaseData.database_port.required" class="text-danger"> | ||||
|               {{ $tc('validation.required') }} | ||||
|             </span> | ||||
|             <span v-if="!$v.databaseData.database_port.numeric" class="text-danger"> | ||||
|               {{ $tc('validation.numbers_only') }} | ||||
|             </span> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.database.db_name') }}</label> | ||||
|           <span class="text-danger"> *</span> | ||||
|           <base-input | ||||
|             :invalid="$v.databaseData.database_name.$error" | ||||
|             v-model.trim="databaseData.database_name" | ||||
|             type="text" | ||||
|             name="database_name" | ||||
|             @input="$v.databaseData.database_name.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.databaseData.database_name.$error"> | ||||
|             <span v-if="!$v.databaseData.database_name.required" class="text-danger"> | ||||
|               {{ $tc('validation.required') }} | ||||
|             </span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.database.username') }}</label> | ||||
|           <span class="text-danger"> *</span> | ||||
|           <base-input | ||||
|             :invalid="$v.databaseData.database_username.$error" | ||||
|             v-model.trim="databaseData.database_username" | ||||
|             type="text" | ||||
|             name="database_username" | ||||
|             @input="$v.databaseData.database_username.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.databaseData.database_username.$error"> | ||||
|             <span v-if="!$v.databaseData.database_username.required" class="text-danger"> | ||||
|               {{ $tc('validation.required') }} | ||||
|             </span> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.database.password') }}</label> | ||||
|           <span class="text-danger"> *</span> | ||||
|           <base-input | ||||
|             v-model.trim="databaseData.database_password" | ||||
|             type="password" | ||||
|             name="name" | ||||
|           /> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.database.host') }}</label> | ||||
|           <span class="text-danger"> *</span> | ||||
|           <base-input | ||||
|             :invalid="$v.databaseData.database_hostname.$error" | ||||
|             v-model.trim="databaseData.database_hostname" | ||||
|             type="text" | ||||
|             name="database_hostname" | ||||
|             @input="$v.databaseData.database_hostname.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.databaseData.database_hostname.$error"> | ||||
|             <span v-if="!$v.databaseData.database_hostname.required" class="text-danger"> | ||||
|               {{ $tc('validation.required') }} | ||||
|             </span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <base-button | ||||
|         :loading="loading" | ||||
|         class="pull-right mt-5" | ||||
|         icon="save" | ||||
|         color="theme" | ||||
|         type="submit" | ||||
|       > | ||||
|         {{ $t('wizard.save_cont') }} | ||||
|       </base-button> | ||||
|     </form> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import MultiSelect from 'vue-multiselect' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| const { required, numeric, url } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     MultiSelect | ||||
|   }, | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       databaseData: { | ||||
|         database_connection: 'mysql', | ||||
|         database_hostname: '127.0.0.1', | ||||
|         database_port: '3306', | ||||
|         database_name: null, | ||||
|         database_username: null, | ||||
|         database_password: null, | ||||
|         app_url: null | ||||
|       }, | ||||
|       loading: false, | ||||
|       connections: [ | ||||
|         'sqlite', | ||||
|         'mysql', | ||||
|         'pgsql', | ||||
|         'sqlsrv' | ||||
|       ] | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     databaseData: { | ||||
|       database_connection: { | ||||
|         required | ||||
|       }, | ||||
|       database_hostname: { | ||||
|         required | ||||
|       }, | ||||
|       database_port: { | ||||
|         required, | ||||
|         numeric | ||||
|       }, | ||||
|       database_name: { | ||||
|         required | ||||
|       }, | ||||
|       database_username: { | ||||
|         required | ||||
|       }, | ||||
|       app_url: { | ||||
|         required, | ||||
|         url | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     async next () { | ||||
|       this.$v.databaseData.$touch() | ||||
|       if (this.$v.databaseData.$invalid) { | ||||
|         return true | ||||
|       } | ||||
|       this.loading = true | ||||
|       try { | ||||
|         let response = await window.axios.post('/api/admin/onboarding/environment/database', this.databaseData) | ||||
|         if (response.data.success) { | ||||
|           this.$emit('next') | ||||
|           window.toastr['success'](this.$t('wizard.success.' + response.data.success)) | ||||
|           return true | ||||
|         } else { | ||||
|           window.toastr['error'](this.$t('wizard.errors.' + response.data.error)) | ||||
|         } | ||||
|         this.loading = false | ||||
|       } catch (e) { | ||||
|         console.log(e) | ||||
|         window.toastr['error']('Somethig went wrong') | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										208
									
								
								resources/assets/js/views/wizard/EmailConfiguration.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								resources/assets/js/views/wizard/EmailConfiguration.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,208 @@ | ||||
| <template> | ||||
|   <div class="card-body"> | ||||
|     <form action="" @submit.prevent="next()"> | ||||
|       <p class="form-title">{{ $t('wizard.mail.mail_config') }}</p> | ||||
|       <p class="form-desc">{{ $t('wizard.mail.mail_config_desc') }}</p> | ||||
|       <div class="row my-2 mt-5"> | ||||
|         <div class="col-md-6 my-2"> | ||||
|           <label class="form-label">{{ $t('wizard.mail.driver') }}</label> | ||||
|           <span class="text-danger"> *</span> | ||||
|           <base-select | ||||
|             v-model="mailConfigData.mail_driver" | ||||
|             :invalid="$v.mailConfigData.mail_driver.$error" | ||||
|             :options="mail_drivers" | ||||
|             :searchable="true" | ||||
|             :show-labels="false" | ||||
|             @change="$v.mailConfigData.mail_driver.$touch()" | ||||
|           /> | ||||
|           <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('wizard.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('wizard.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('wizard.mail.password') }}</label> | ||||
|           <span class="text-danger"> *</span> | ||||
|           <base-input | ||||
|             :invalid="$v.mailConfigData.mail_password.$error" | ||||
|             v-model.trim="mailConfigData.mail_password" | ||||
|             type="mail_password" | ||||
|             name="name" | ||||
|             @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('wizard.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('wizard.mail.encryption') }}</label> | ||||
|           <span class="text-danger"> *</span> | ||||
|           <base-input | ||||
|             :invalid="$v.mailConfigData.mail_encryption.$error" | ||||
|             v-model.trim="mailConfigData.mail_encryption" | ||||
|             type="text" | ||||
|             name="name" | ||||
|             @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> | ||||
|       <base-button | ||||
|         :loading="loading" | ||||
|         class="pull-right mt-5" | ||||
|         icon="save" | ||||
|         color="theme" | ||||
|         type="submit" | ||||
|       > | ||||
|         {{ $t('wizard.save_cont') }} | ||||
|       </base-button> | ||||
|     </form> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import MultiSelect from 'vue-multiselect' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import Ls from '../../services/ls' | ||||
| const { required, email, numeric } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     MultiSelect | ||||
|   }, | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       mailConfigData: { | ||||
|         mail_driver: 'smtp', | ||||
|         mail_host: 'mailtrap.io', | ||||
|         mail_port: 2525, | ||||
|         mail_username: 'cc3c64516febd4', | ||||
|         mail_password: 'e6a0176301f587', | ||||
|         mail_encryption: 'tls' | ||||
|       }, | ||||
|       loading: false, | ||||
|       mail_drivers: [] | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     mailConfigData: { | ||||
|       mail_driver: { | ||||
|         required | ||||
|       }, | ||||
|       mail_host: { | ||||
|         required | ||||
|       }, | ||||
|       mail_port: { | ||||
|         required, | ||||
|         numeric | ||||
|       }, | ||||
|       mail_username: { | ||||
|         required | ||||
|       }, | ||||
|       mail_password: { | ||||
|         required | ||||
|       }, | ||||
|       mail_encryption: { | ||||
|         required | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     this.getMailDrivers() | ||||
|   }, | ||||
|   methods: { | ||||
|     async getMailDrivers () { | ||||
|       this.loading = true | ||||
|  | ||||
|       let response = await window.axios.get('/api/admin/onboarding/environment/mail') | ||||
|  | ||||
|       if (response.data) { | ||||
|         this.mail_drivers = response.data | ||||
|         this.loading = false | ||||
|       } | ||||
|     }, | ||||
|     async next () { | ||||
|       this.$v.mailConfigData.$touch() | ||||
|       if (this.$v.mailConfigData.$invalid) { | ||||
|         return true | ||||
|       } | ||||
|       this.loading = true | ||||
|       try { | ||||
|         let response = await window.axios.post('/api/admin/onboarding/environment/mail', this.mailConfigData) | ||||
|         if (response.data.success) { | ||||
|           this.$emit('next') | ||||
|           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']('Somethig went wrong') | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										115
									
								
								resources/assets/js/views/wizard/Index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								resources/assets/js/views/wizard/Index.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,115 @@ | ||||
| <template> | ||||
|   <div class="wizard"> | ||||
|     <div class="step-indicator"> | ||||
|       <img | ||||
|         id="logo-crater" | ||||
|         src="/assets/img/crater-logo.png" | ||||
|         alt="Crater Logo" | ||||
|         width="225" | ||||
|         height="50" | ||||
|         class="logo" | ||||
|       > | ||||
|       <div class="indicator-line"> | ||||
|         <div class="center"> | ||||
|           <div class="steps" :class="{'active': step === 1, 'completed': step > 1}"> | ||||
|             <font-awesome-icon v-if="step > 1" icon="check" class="icon-check"/> | ||||
|           </div> | ||||
|           <div class="steps" :class="{'active': step === 2, 'completed': step > 2}"> | ||||
|             <font-awesome-icon v-if="step > 2" icon="check" class="icon-check"/> | ||||
|           </div> | ||||
|           <div class="steps" :class="{'active': step === 3, 'completed': step > 3}"> | ||||
|             <font-awesome-icon v-if="step > 3" icon="check" class="icon-check"/> | ||||
|           </div> | ||||
|           <div class="steps" :class="{'active': step === 4, 'completed': step > 4}"> | ||||
|             <font-awesome-icon v-if="step > 4" icon="check" class="icon-check"/> | ||||
|           </div> | ||||
|           <div class="steps" :class="{'active': step === 5, 'completed': step > 5}"> | ||||
|             <font-awesome-icon v-if="step > 5" icon="check" class="icon-check"/> | ||||
|           </div> | ||||
|           <div class="steps" :class="{'active': step === 6, 'completed': step > 6}"> | ||||
|             <font-awesome-icon v-if="step > 6" icon="check" class="icon-check"/> | ||||
|           </div> | ||||
|           <div class="steps" :class="{'active': step === 7, 'completed': step > 7}"> | ||||
|             <font-awesome-icon v-if="step > 7" icon="check" class="icon-check"/> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="form-content"> | ||||
|       <div class="card wizard-card"> | ||||
|         <component | ||||
|           :is="tab" | ||||
|           @next="setTab" | ||||
|         /> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import SystemRequirement from './SystemRequirement' | ||||
| import Permission from './Permission' | ||||
| import Database from './Database' | ||||
| import EmailConfiguration from './EmailConfiguration' | ||||
| import UserProfile from './UserProfile' | ||||
| import CompanyInfo from './CompanyInfo' | ||||
| import Settings from './Settings' | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     step_1: SystemRequirement, | ||||
|     step_2: Permission, | ||||
|     step_3: Database, | ||||
|     step_4: EmailConfiguration, | ||||
|     step_5: UserProfile, | ||||
|     step_6: CompanyInfo, | ||||
|     step_7: Settings | ||||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       loading: false, | ||||
|       tab: 'step_1', | ||||
|       step: 1 | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     this.getOnboardingData() | ||||
|   }, | ||||
|   methods: { | ||||
|     async getOnboardingData () { | ||||
|       let response = await window.axios.get('/api/admin/onboarding') | ||||
|       if (response.data) { | ||||
|         if (response.data.profile_complete === 'COMPLETED') { | ||||
|           this.$router.push('/admin/dashboard') | ||||
|  | ||||
|           return | ||||
|         } | ||||
|  | ||||
|         let dbStep = parseInt(response.data.profile_complete) | ||||
|  | ||||
|         if (dbStep) { | ||||
|           this.step = dbStep + 1 | ||||
|           this.tab = `step_${dbStep + 1}` | ||||
|         } | ||||
|  | ||||
|         this.languages = response.data.languages | ||||
|         this.currencies = response.data.currencies | ||||
|         this.dateFormats = response.data.date_formats | ||||
|         this.timeZones = response.data.time_zones | ||||
|  | ||||
|         // this.settingData.currency = this.currencies.find(currency => currency.id === 1) | ||||
|         // this.settingData.language = this.languages.find(language => language.code === 'en') | ||||
|         // this.settingData.dateFormat = this.dateFormats.find(dateFormat => dateFormat.value === 'd M Y') | ||||
|       } | ||||
|     }, | ||||
|     setTab (data) { | ||||
|       this.step++ | ||||
|  | ||||
|       if (this.step <= 7) { | ||||
|         this.tab = 'step_' + this.step | ||||
|       } else { | ||||
|         // window.location.reload() | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										69
									
								
								resources/assets/js/views/wizard/Permission.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								resources/assets/js/views/wizard/Permission.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,69 @@ | ||||
| <template> | ||||
|   <div class="card-body permissions"> | ||||
|     <p class="form-title">{{ $t('wizard.permissions.permissions') }}</p> | ||||
|     <p class="form-desc">{{ $t('wizard.permissions.permission_desc') }}</p> | ||||
|     <div class="d-flex justify-content-start"> | ||||
|       <div class="lists col-md-6"> | ||||
|         <div | ||||
|           v-for="(permission, index) in permissions" | ||||
|           :key="index" | ||||
|           class="row list-items" | ||||
|         > | ||||
|  | ||||
|           <div class="col-sm-9 left-item"> | ||||
|             {{ permission.folder }} | ||||
|           </div> | ||||
|           <div class="col-sm-3 right-item"> | ||||
|             <span v-if="permission.isSet" class="verified"/> | ||||
|             <span v-else class="not-verified"/> | ||||
|             <span>{{ permission.permission }}</span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <base-button | ||||
|       v-if="!errors" | ||||
|       class="pull-right mt-5" | ||||
|       icon="arrow-right" | ||||
|       right-icon | ||||
|       color="theme" | ||||
|       @click="next" | ||||
|     > | ||||
|       {{ $t('wizard.continue') }} | ||||
|     </base-button> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import Ls from '../../services/ls' | ||||
|  | ||||
| export default { | ||||
|   data () { | ||||
|     return { | ||||
|       loading: false, | ||||
|       permissions: [], | ||||
|       errors: false | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     this.getPermissions() | ||||
|   }, | ||||
|   methods: { | ||||
|     async getPermissions () { | ||||
|       this.loading = true | ||||
|  | ||||
|       let response = await window.axios.get('/api/admin/onboarding/permissions', this.profileData) | ||||
|  | ||||
|       if (response.data) { | ||||
|         this.permissions = response.data.permissions.permissions | ||||
|         this.errors = response.data.permissions.errors | ||||
|         this.loading = false | ||||
|       } | ||||
|     }, | ||||
|     async next () { | ||||
|       this.loading = true | ||||
|       await this.$emit('next') | ||||
|       this.loading = false | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										209
									
								
								resources/assets/js/views/wizard/Settings.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								resources/assets/js/views/wizard/Settings.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,209 @@ | ||||
| <template> | ||||
|   <div class="card-body"> | ||||
|     <form action="" @submit.prevent="next()"> | ||||
|       <p class="form-title">{{ $t('wizard.preferences') }}</p> | ||||
|       <p class="form-desc">{{ $t('wizard.preferences_desc') }}</p> | ||||
|       <div class="row"> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.currency') }}</label> | ||||
|           <span class="text-danger"> *</span> | ||||
|           <base-select | ||||
|             v-model="settingData.currency" | ||||
|             :class="{'error': $v.settingData.currency.$error }" | ||||
|             :options="currencies" | ||||
|             :searchable="true" | ||||
|             :show-labels="false" | ||||
|             :placeholder="$t('settings.currencies.select_currency')" | ||||
|             track-by="id" | ||||
|             label="name" | ||||
|             @input="$v.settingData.currency.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.settingData.currency.$error"> | ||||
|             <span v-if="!$v.settingData.currency.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.language') }}</label><span class="text-danger"> *</span> | ||||
|           <base-select | ||||
|             v-model="settingData.language" | ||||
|             :class="{'error': $v.settingData.language.$error }" | ||||
|             :options="languages" | ||||
|             :searchable="true" | ||||
|             :show-labels="false" | ||||
|             :placeholder="$t('settings.preferences.select_language')" | ||||
|             label="name" | ||||
|             @input="$v.settingData.language.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.settingData.language.$error"> | ||||
|             <span v-if="!$v.settingData.language.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.date_format') }}</label><span class="text-danger"> *</span> | ||||
|           <base-select | ||||
|             v-model="settingData.dateFormat" | ||||
|             :class="{'error': $v.settingData.dateFormat.$error }" | ||||
|             :options="dateFormats" | ||||
|             :searchable="true" | ||||
|             :show-labels="false" | ||||
|             :placeholder="$t('settings.preferences.select_date_formate')" | ||||
|             label="display_date" | ||||
|             @input="$v.settingData.dateFormat.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.settingData.dateFormat.$error"> | ||||
|             <span v-if="!$v.settingData.dateFormat.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.time_zone') }}</label><span class="text-danger"> *</span> | ||||
|           <base-select | ||||
|             v-model="settingData.timeZone" | ||||
|             :class="{'error': $v.settingData.timeZone.$error }" | ||||
|             :options="timeZones" | ||||
|             :searchable="true" | ||||
|             :show-labels="false" | ||||
|             :placeholder="$t('settings.preferences.select_date_formate')" | ||||
|             label="key" | ||||
|             @input="$v.settingData.timeZone.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.settingData.timeZone.$error"> | ||||
|             <span v-if="!$v.settingData.timeZone.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.fiscal_year') }}</label><span class="text-danger"> *</span> | ||||
|           <base-select | ||||
|             v-model="settingData.fiscalYear" | ||||
|             :class="{'error': $v.settingData.fiscalYear.$error }" | ||||
|             :options="fiscalYears" | ||||
|             :searchable="true" | ||||
|             :show-labels="false" | ||||
|             :placeholder="$t('settings.preferences.select_financial_year')" | ||||
|             label="key" | ||||
|             @input="$v.settingData.fiscalYear.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.settingData.fiscalYear.$error"> | ||||
|             <span v-if="!$v.settingData.fiscalYear.required" class="text-danger">{{ $tc('customers.errors.required') }}</span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <base-button :loading="loading" class="pull-right" icon="save" color="theme" type="submit"> | ||||
|         {{ $t('wizard.save_cont') }} | ||||
|       </base-button> | ||||
|     </form> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import MultiSelect from 'vue-multiselect' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import Ls from '../../services/ls' | ||||
| const { required, minLength, email } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     MultiSelect | ||||
|   }, | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       settingData: { | ||||
|         language: null, | ||||
|         currency: null, | ||||
|         timeZone: null, | ||||
|         dateFormat: null, | ||||
|         fiscalYear: null | ||||
|       }, | ||||
|       loading: false, | ||||
|       step: 1, | ||||
|       languages: [], | ||||
|       currencies: [], | ||||
|       timeZones: [], | ||||
|       dateFormats: [], | ||||
|       fiscalYears: [] | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     settingData: { | ||||
|       currency: { | ||||
|         required | ||||
|       }, | ||||
|       language: { | ||||
|         required | ||||
|       }, | ||||
|       dateFormat: { | ||||
|         required | ||||
|       }, | ||||
|       timeZone: { | ||||
|         required | ||||
|       }, | ||||
|       fiscalYear: { | ||||
|         required | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     this.getOnboardingData() | ||||
|   }, | ||||
|   methods: { | ||||
|     async getOnboardingData () { | ||||
|       let response = await window.axios.get('/api/admin/onboarding') | ||||
|       if (response.data) { | ||||
|         if (response.data.profile_complete === 'COMPLETED') { | ||||
|           this.$router.push('/admin/dashboard') | ||||
|  | ||||
|           return | ||||
|         } | ||||
|  | ||||
|         let dbStep = parseInt(response.data.profile_complete) | ||||
|  | ||||
|         if (dbStep) { | ||||
|           this.step = dbStep + 1 | ||||
|         } | ||||
|  | ||||
|         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.settingData.currency = this.currencies.find(currency => currency.id === 1) | ||||
|         this.settingData.language = this.languages.find(language => language.code === 'en') | ||||
|         this.settingData.dateFormat = response.data.date_formats.find(dateFormat => dateFormat.carbon_format_value == 'd M Y') | ||||
|         this.settingData.timeZone = this.timeZones.find(timeZone => timeZone.value === 'UTC') | ||||
|         this.settingData.fiscalYear = this.fiscalYears.find(fiscalYear => fiscalYear.value === '1-12') | ||||
|       } | ||||
|     }, | ||||
|     async next () { | ||||
|       this.$v.settingData.$touch() | ||||
|  | ||||
|       if (this.$v.settingData.$invalid) { | ||||
|         return true | ||||
|       } | ||||
|  | ||||
|       this.loading = true | ||||
|  | ||||
|       let data = { | ||||
|         currency: this.settingData.currency.id, | ||||
|         time_zone: this.settingData.timeZone.value, | ||||
|         language: this.settingData.language.code, | ||||
|         fiscal_year: this.settingData.fiscalYear.value, | ||||
|         carbon_date_format: this.settingData.dateFormat.carbon_format_value, | ||||
|         moment_date_format: this.settingData.dateFormat.moment_format_value | ||||
|       } | ||||
|  | ||||
|       let response = await window.axios.post('/api/admin/onboarding/settings', data) | ||||
|  | ||||
|       if (response.data) { | ||||
|         // this.$emit('next') | ||||
|         this.loading = false | ||||
|         Ls.set('auth.token', response.data.token) | ||||
|         this.$router.push('/admin/dashboard') | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										99
									
								
								resources/assets/js/views/wizard/SystemRequirement.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								resources/assets/js/views/wizard/SystemRequirement.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,99 @@ | ||||
| <template> | ||||
|   <div class="card-body"> | ||||
|     <p class="form-title">{{ $t('wizard.req.system_req') }}</p> | ||||
|     <p class="form-desc">{{ $t('wizard.req.system_req_desc') }}</p> | ||||
|     <div v-if="phpSupportInfo" class="d-flex justify-content-start"> | ||||
|       <div class="col-md-6"> | ||||
|         <div class="row list-items"> | ||||
|           <div class="col-md-9 left-item"> | ||||
|             {{ $t('wizard.req.php_req_version', { version: phpSupportInfo.minimum }) }} | ||||
|           </div> | ||||
|           <div class="col-md-3 right-item justify-content-end"> | ||||
|             {{ phpSupportInfo.current }} | ||||
|             <span v-if="phpSupportInfo.supported" class="verified"/> | ||||
|             <span v-else class="not-verified"/> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div v-if="requirements" class="d-flex justify-content-start"> | ||||
|       <div class="col-md-6"> | ||||
|         <div | ||||
|           v-for="(requirement, index) in requirements" | ||||
|           :key="index" | ||||
|           class="row list-items" | ||||
|         > | ||||
|  | ||||
|           <div class="col-md-9 left-item"> | ||||
|             {{ index }} | ||||
|           </div> | ||||
|           <div class="col-md-3 right-item  justify-content-end"> | ||||
|             <span v-if="requirement" class="verified"/> | ||||
|             <span v-else class="not-verified"/> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <base-button | ||||
|       v-if="requirements" | ||||
|       :loading="loading" | ||||
|       class="pull-right mt-4" | ||||
|       icon="arrow-right" | ||||
|       color="theme" | ||||
|       right-icon | ||||
|       @click="next" | ||||
|     > | ||||
|       {{ $t('wizard.continue') }} | ||||
|     </base-button> | ||||
|     <base-button | ||||
|       v-else | ||||
|       :loading="loading" | ||||
|       class="pull-right mt-4" | ||||
|       color="theme" | ||||
|       @click="getRequirements" | ||||
|     > | ||||
|       {{ $t('wizard.req.check_req') }} | ||||
|     </base-button> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import MultiSelect from 'vue-multiselect' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import Ls from '../../services/ls' | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     MultiSelect | ||||
|   }, | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       requirements: null, | ||||
|       phpSupportInfo: null, | ||||
|       loading: false, | ||||
|       isShow: true | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     listToggle  () { | ||||
|       this.isShow = !this.isShow | ||||
|     }, | ||||
|     async getRequirements () { | ||||
|       this.loading = true | ||||
|  | ||||
|       let response = await window.axios.get('/api/admin/onboarding/requirements', this.profileData) | ||||
|  | ||||
|       if (response.data) { | ||||
|         this.requirements = response.data.requirements.requirements.php | ||||
|         this.phpSupportInfo = response.data.phpSupportInfo | ||||
|         this.loading = false | ||||
|       } | ||||
|     }, | ||||
|     async next () { | ||||
|       this.loading = true | ||||
|       await this.$emit('next') | ||||
|       this.loading = false | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										141
									
								
								resources/assets/js/views/wizard/UserProfile.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								resources/assets/js/views/wizard/UserProfile.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,141 @@ | ||||
| <template> | ||||
|   <div class="card-body"> | ||||
|     <form action="" @submit.prevent="next()"> | ||||
|       <p class="form-title">{{ $t('wizard.account_info') }}</p> | ||||
|       <p class="form-desc">{{ $t('wizard.account_info_desc') }}</p> | ||||
|       <div class="row"> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.name') }}</label><span class="text-danger"> *</span> | ||||
|           <base-input | ||||
|             :invalid="$v.profileData.name.$error" | ||||
|             v-model.trim="profileData.name" | ||||
|             type="text" | ||||
|             name="name" | ||||
|             @input="$v.profileData.name.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.profileData.name.$error"> | ||||
|             <span v-if="!$v.profileData.name.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|             <span v-if="!$v.profileData.name.minLength" class="text-danger"> {{ $tc('validation.name_min_length', $v.profileData.name.$params.minLength.min, { count: $v.profileData.name.$params.minLength.min }) }} </span> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.email') }}</label><span class="text-danger"> *</span> | ||||
|           <base-input | ||||
|             :invalid="$v.profileData.email.$error" | ||||
|             v-model.trim="profileData.email" | ||||
|             type="text" | ||||
|             name="email" | ||||
|             @input="$v.profileData.email.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.profileData.email.$error"> | ||||
|             <span v-if="!$v.profileData.email.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|             <span v-if="!$v.profileData.email.email" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.password') }}</label><span class="text-danger"> *</span> | ||||
|           <base-input | ||||
|             :invalid="$v.profileData.password.$error" | ||||
|             v-model.trim="profileData.password" | ||||
|             type="password" | ||||
|             name="password" | ||||
|             @input="$v.profileData.password.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.profileData.password.$error"> | ||||
|             <span v-if="!$v.profileData.password.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.confirm_password') }}</label><span class="text-danger"> *</span> | ||||
|           <base-input | ||||
|             :invalid="$v.profileData.confirm_password.$error" | ||||
|             v-model.trim="profileData.confirm_password" | ||||
|             type="password" | ||||
|             name="confirm_password" | ||||
|             @input="$v.profileData.confirm_password.$touch()" | ||||
|           /> | ||||
|           <div v-if="$v.profileData.confirm_password.$error"> | ||||
|             <span v-if="!$v.profileData.confirm_password.sameAsPassword" class="text-danger">{{ $tc('validation.password_incorrect') }}</span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <base-button | ||||
|         :loading="loading" | ||||
|         class="pull-right mt-4" | ||||
|         icon="save" | ||||
|         color="theme" | ||||
|         type="submit" | ||||
|       > | ||||
|         {{ $t('wizard.save_cont') }} | ||||
|       </base-button> | ||||
|     </form> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import MultiSelect from 'vue-multiselect' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import Ls from '../../services/ls' | ||||
| const { required, requiredIf, sameAs, minLength, email } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     MultiSelect | ||||
|   }, | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       profileData: { | ||||
|         name: null, | ||||
|         email: null, | ||||
|         password: null, | ||||
|         confirm_password: null | ||||
|       }, | ||||
|       loading: false | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
|     profileData: { | ||||
|       name: { | ||||
|         required, | ||||
|         minLength: minLength(3) | ||||
|       }, | ||||
|       email: { | ||||
|         email, | ||||
|         required | ||||
|       }, | ||||
|       password: { | ||||
|         required | ||||
|       }, | ||||
|       confirm_password: { | ||||
|         required: requiredIf('isRequired'), | ||||
|         sameAsPassword: sameAs('password') | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     isRequired () { | ||||
|       if (this.profileData.password === null || this.profileData.password === undefined || this.profileData.password === '') { | ||||
|         return false | ||||
|       } | ||||
|       return true | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     async next () { | ||||
|       this.$v.profileData.$touch() | ||||
|       if (this.$v.profileData.$invalid) { | ||||
|         return true | ||||
|       } | ||||
|       this.loading = true | ||||
|       let response = await window.axios.post('/api/admin/onboarding/profile', this.profileData) | ||||
|       if (response.data) { | ||||
|         this.$emit('next') | ||||
|         this.loading = false | ||||
|       } | ||||
|       return true | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
		Reference in New Issue
	
	Block a user