mirror of
				https://github.com/crater-invoice/crater.git
				synced 2025-10-31 05:31:10 -04:00 
			
		
		
		
	v5.0.0 update
This commit is contained in:
		
							
								
								
									
										222
									
								
								resources/scripts/views/customers/partials/CustomerChart.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								resources/scripts/views/customers/partials/CustomerChart.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,222 @@ | ||||
| <template> | ||||
|   <BaseCard class="flex flex-col mt-6"> | ||||
|     <ChartPlaceholder v-if="customerStore.isFetchingViewData" /> | ||||
|  | ||||
|     <div v-else class="grid grid-cols-12"> | ||||
|       <div class="col-span-12 xl:col-span-9 xxl:col-span-10"> | ||||
|         <div class="flex justify-between mt-1 mb-6"> | ||||
|           <h6 class="flex items-center"> | ||||
|             <BaseIcon name="ChartSquareBarIcon" class="h-5 text-primary-400" /> | ||||
|             {{ $t('dashboard.monthly_chart.title') }} | ||||
|           </h6> | ||||
|  | ||||
|           <div class="w-40 h-10"> | ||||
|             <BaseMultiselect | ||||
|               v-model="selectedYear" | ||||
|               :options="years" | ||||
|               :allow-empty="false" | ||||
|               :show-labels="false" | ||||
|               :placeholder="$t('dashboard.select_year')" | ||||
|               :can-deselect="false" | ||||
|               @select="onChangeYear" | ||||
|             /> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <LineChart | ||||
|           v-if="isLoading" | ||||
|           :invoices="getChartInvoices" | ||||
|           :expenses="getChartExpenses" | ||||
|           :receipts="getReceiptTotals" | ||||
|           :income="getNetProfits" | ||||
|           :labels="getChartMonths" | ||||
|           class="sm:w-full" | ||||
|         /> | ||||
|       </div> | ||||
|  | ||||
|       <div | ||||
|         class=" | ||||
|           grid | ||||
|           col-span-12 | ||||
|           mt-6 | ||||
|           text-center | ||||
|           xl:mt-0 | ||||
|           sm:grid-cols-4 | ||||
|           xl:text-right xl:col-span-3 xl:grid-cols-1 | ||||
|           xxl:col-span-2 | ||||
|         " | ||||
|       > | ||||
|         <div class="px-6 py-2"> | ||||
|           <span class="text-xs leading-5 lg:text-sm"> | ||||
|             {{ $t('dashboard.chart_info.total_sales') }} | ||||
|           </span> | ||||
|           <br /> | ||||
|           <span | ||||
|             v-if="isLoading" | ||||
|             class="block mt-1 text-xl font-semibold leading-8" | ||||
|           > | ||||
|             <BaseFormatMoney | ||||
|               :amount="chartData.salesTotal" | ||||
|               :currency="data.currency" | ||||
|             /> | ||||
|           </span> | ||||
|         </div> | ||||
|  | ||||
|         <div class="px-6 py-2"> | ||||
|           <span class="text-xs leading-5 lg:text-sm"> | ||||
|             {{ $t('dashboard.chart_info.total_receipts') }} | ||||
|           </span> | ||||
|           <br /> | ||||
|  | ||||
|           <span | ||||
|             v-if="isLoading" | ||||
|             class="block mt-1 text-xl font-semibold leading-8" | ||||
|             style="color: #00c99c" | ||||
|           > | ||||
|             <BaseFormatMoney | ||||
|               :amount="chartData.totalExpenses" | ||||
|               :currency="data.currency" | ||||
|             /> | ||||
|           </span> | ||||
|         </div> | ||||
|  | ||||
|         <div class="px-6 py-2"> | ||||
|           <span class="text-xs leading-5 lg:text-sm"> | ||||
|             {{ $t('dashboard.chart_info.total_expense') }} | ||||
|           </span> | ||||
|           <br /> | ||||
|           <span | ||||
|             v-if="isLoading" | ||||
|             class="block mt-1 text-xl font-semibold leading-8" | ||||
|             style="color: #fb7178" | ||||
|           > | ||||
|             <BaseFormatMoney | ||||
|               :amount="chartData.totalExpenses" | ||||
|               :currency="data.currency" | ||||
|             /> | ||||
|           </span> | ||||
|         </div> | ||||
|  | ||||
|         <div class="px-6 py-2"> | ||||
|           <span class="text-xs leading-5 lg:text-sm"> | ||||
|             {{ $t('dashboard.chart_info.net_income') }} | ||||
|           </span> | ||||
|           <br /> | ||||
|           <span | ||||
|             v-if="isLoading" | ||||
|             class="block mt-1 text-xl font-semibold leading-8" | ||||
|             style="color: #5851d8" | ||||
|           > | ||||
|             <BaseFormatMoney | ||||
|               :amount="chartData.netProfit" | ||||
|               :currency="data.currency" | ||||
|             /> | ||||
|           </span> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <CustomerInfo /> | ||||
|   </BaseCard> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| import CustomerInfo from './CustomerInfo.vue' | ||||
| import LineChart from '@/scripts/components/charts/LineChart.vue' | ||||
| import { ref, computed, watch, reactive, inject } from 'vue' | ||||
| import { useCustomerStore } from '@/scripts/stores/customer' | ||||
| import { useRoute } from 'vue-router' | ||||
| import { useCompanyStore } from '@/scripts/stores/company' | ||||
| import ChartPlaceholder from './CustomerChartPlaceholder.vue' | ||||
|  | ||||
| const companyStore = useCompanyStore() | ||||
| const customerStore = useCustomerStore() | ||||
| const utils = inject('utils') | ||||
|  | ||||
| const route = useRoute() | ||||
|  | ||||
| let isLoading = ref(false) | ||||
| let chartData = reactive({}) | ||||
| let data = reactive({}) | ||||
| let years = reactive(['This year', 'Previous year']) | ||||
| let selectedYear = ref('This year') | ||||
|  | ||||
| const getChartExpenses = computed(() => { | ||||
|   if (chartData.expenseTotals) { | ||||
|     return chartData.expenseTotals | ||||
|   } | ||||
|   return [] | ||||
| }) | ||||
|  | ||||
| const getNetProfits = computed(() => { | ||||
|   if (chartData.netProfits) { | ||||
|     return chartData.netProfits | ||||
|   } | ||||
|   return [] | ||||
| }) | ||||
|  | ||||
| const getChartMonths = computed(() => { | ||||
|   if (chartData && chartData.months) { | ||||
|     return chartData.months | ||||
|   } | ||||
|   return [] | ||||
| }) | ||||
|  | ||||
| const getReceiptTotals = computed(() => { | ||||
|   if (chartData.receiptTotals) { | ||||
|     return chartData.receiptTotals | ||||
|   } | ||||
|   return [] | ||||
| }) | ||||
|  | ||||
| const getChartInvoices = computed(() => { | ||||
|   if (chartData.invoiceTotals) { | ||||
|     return chartData.invoiceTotals | ||||
|   } | ||||
|  | ||||
|   return [] | ||||
| }) | ||||
|  | ||||
| watch( | ||||
|   route, | ||||
|   () => { | ||||
|     if (route.params.id) { | ||||
|       loadCustomer() | ||||
|     } | ||||
|     selectedYear.value = 'This year' | ||||
|   }, | ||||
|   { immediate: true } | ||||
| ) | ||||
|  | ||||
| async function loadCustomer() { | ||||
|   isLoading.value = false | ||||
|   let response = await customerStore.fetchViewCustomer({ | ||||
|     id: route.params.id, | ||||
|   }) | ||||
|  | ||||
|   if (response.data) { | ||||
|     Object.assign(chartData, response.data.meta.chartData) | ||||
|     Object.assign(data, response.data.data) | ||||
|   } | ||||
|  | ||||
|   isLoading.value = true | ||||
| } | ||||
|  | ||||
| async function onChangeYear(data) { | ||||
|   let params = { | ||||
|     id: route.params.id, | ||||
|   } | ||||
|  | ||||
|   data === 'Previous year' | ||||
|     ? (params.previous_year = true) | ||||
|     : (params.this_year = true) | ||||
|  | ||||
|   let response = await customerStore.fetchViewCustomer(params) | ||||
|  | ||||
|   if (response.data.meta.chartData) { | ||||
|     Object.assign(chartData, response.data.meta.chartData) | ||||
|   } | ||||
|  | ||||
|   return true | ||||
| } | ||||
| </script> | ||||
| @ -0,0 +1,79 @@ | ||||
| <template> | ||||
|   <BaseContentPlaceholders class="grid grid-cols-12"> | ||||
|     <div class="col-span-12 xl:col-span-9 xxl:col-span-10"> | ||||
|       <div class="flex justify-between mt-1 mb-6"> | ||||
|         <BaseContentPlaceholdersText class="h-10 w-36" :lines="1" /> | ||||
|         <BaseContentPlaceholdersText class="h-10 w-40 !mt-0" :lines="1" /> | ||||
|       </div> | ||||
|       <BaseContentPlaceholdersBox class="h-80 xl:h-72 sm:w-full" /> | ||||
|     </div> | ||||
|  | ||||
|     <div | ||||
|       class=" | ||||
|         grid | ||||
|         col-span-12 | ||||
|         mt-6 | ||||
|         text-center | ||||
|         xl:mt-0 | ||||
|         sm:grid-cols-4 | ||||
|         xl:text-right xl:col-span-3 xl:grid-cols-1 | ||||
|         xxl:col-span-2 | ||||
|       " | ||||
|     > | ||||
|       <div | ||||
|         class=" | ||||
|           flex flex-col | ||||
|           items-center | ||||
|           justify-center | ||||
|           px-6 | ||||
|           py-2 | ||||
|           lg:justify-end lg:items-end | ||||
|         " | ||||
|       > | ||||
|         <BaseContentPlaceholdersText class="h-3 w-14 xl:h-4" :lines="1" /> | ||||
|         <BaseContentPlaceholdersText class="w-20 h-5 xl:h-6" :lines="1" /> | ||||
|       </div> | ||||
|       <div | ||||
|         class=" | ||||
|           flex flex-col | ||||
|           items-center | ||||
|           justify-center | ||||
|           px-6 | ||||
|           py-2 | ||||
|           lg:justify-end lg:items-end | ||||
|         " | ||||
|       > | ||||
|         <BaseContentPlaceholdersText class="h-3 w-14 xl:h-4" :lines="1" /> | ||||
|         <BaseContentPlaceholdersText class="w-20 h-5 xl:h-6" :lines="1" /> | ||||
|       </div> | ||||
|  | ||||
|       <div | ||||
|         class=" | ||||
|           flex flex-col | ||||
|           items-center | ||||
|           justify-center | ||||
|           px-6 | ||||
|           py-2 | ||||
|           lg:justify-end lg:items-end | ||||
|         " | ||||
|       > | ||||
|         <BaseContentPlaceholdersText class="h-3 w-14 xl:h-4" :lines="1" /> | ||||
|         <BaseContentPlaceholdersText class="w-20 h-5 xl:h-6" :lines="1" /> | ||||
|       </div> | ||||
|  | ||||
|       <div | ||||
|         class=" | ||||
|           flex flex-col | ||||
|           items-center | ||||
|           justify-center | ||||
|           px-6 | ||||
|           py-2 | ||||
|           lg:justify-end lg:items-end | ||||
|         " | ||||
|       > | ||||
|         <BaseContentPlaceholdersText class="h-3 w-14 xl:h-4" :lines="1" /> | ||||
|         <BaseContentPlaceholdersText class="w-20 h-5 xl:h-6" :lines="1" /> | ||||
|       </div> | ||||
|     </div> | ||||
|   </BaseContentPlaceholders> | ||||
| </template> | ||||
							
								
								
									
										119
									
								
								resources/scripts/views/customers/partials/CustomerInfo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								resources/scripts/views/customers/partials/CustomerInfo.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,119 @@ | ||||
| <template> | ||||
|   <div class="pt-6 mt-5 border-t border-solid lg:pt-8 md:pt-4 border-gray-200"> | ||||
|     <!-- Basic Info --> | ||||
|     <BaseHeading> | ||||
|       {{ $t('customers.basic_info') }} | ||||
|     </BaseHeading> | ||||
|  | ||||
|     <BaseDescriptionList> | ||||
|       <BaseDescriptionListItem | ||||
|         :content-loading="contentLoading" | ||||
|         :label="$t('customers.display_name')" | ||||
|         :value="selectedViewCustomer?.name" | ||||
|       /> | ||||
|  | ||||
|       <BaseDescriptionListItem | ||||
|         :content-loading="contentLoading" | ||||
|         :label="$t('customers.primary_contact_name')" | ||||
|         :value="selectedViewCustomer?.contact_name" | ||||
|       /> | ||||
|       <BaseDescriptionListItem | ||||
|         :content-loading="contentLoading" | ||||
|         :label="$t('customers.email')" | ||||
|         :value="selectedViewCustomer?.email" | ||||
|       /> | ||||
|     </BaseDescriptionList> | ||||
|  | ||||
|     <BaseDescriptionList class="mt-5"> | ||||
|       <BaseDescriptionListItem | ||||
|         :content-loading="contentLoading" | ||||
|         :label="$t('wizard.currency')" | ||||
|         :value=" | ||||
|           selectedViewCustomer?.currency | ||||
|             ? `${selectedViewCustomer?.currency?.code} (${selectedViewCustomer?.currency?.symbol})` | ||||
|             : '' | ||||
|         " | ||||
|       /> | ||||
|  | ||||
|       <BaseDescriptionListItem | ||||
|         :content-loading="contentLoading" | ||||
|         :label="$t('customers.phone_number')" | ||||
|         :value="selectedViewCustomer?.phone" | ||||
|       /> | ||||
|       <BaseDescriptionListItem | ||||
|         :content-loading="contentLoading" | ||||
|         :label="$t('customers.website')" | ||||
|         :value="selectedViewCustomer?.website" | ||||
|       /> | ||||
|     </BaseDescriptionList> | ||||
|  | ||||
|     <!-- Address --> | ||||
|     <BaseHeading | ||||
|       v-if="selectedViewCustomer.billing || selectedViewCustomer.shipping" | ||||
|       class="mt-8" | ||||
|     > | ||||
|       {{ $t('customers.address') }} | ||||
|     </BaseHeading> | ||||
|  | ||||
|     <BaseDescriptionList class="mt-5"> | ||||
|       <BaseDescriptionListItem | ||||
|         v-if="selectedViewCustomer.billing" | ||||
|         :content-loading="contentLoading" | ||||
|         :label="$t('customers.billing_address')" | ||||
|       > | ||||
|         <BaseCustomerAddressDisplay :address="selectedViewCustomer.billing" /> | ||||
|       </BaseDescriptionListItem> | ||||
|  | ||||
|       <BaseDescriptionListItem | ||||
|         v-if="selectedViewCustomer.shipping" | ||||
|         :content-loading="contentLoading" | ||||
|         :label="$t('customers.shipping_address')" | ||||
|       > | ||||
|         <BaseCustomerAddressDisplay :address="selectedViewCustomer.shipping" /> | ||||
|       </BaseDescriptionListItem> | ||||
|     </BaseDescriptionList> | ||||
|  | ||||
|     <!-- Custom Fields --> | ||||
|     <BaseHeading v-if="customerCustomFields.length > 0" class="mt-8"> | ||||
|       {{ $t('settings.custom_fields.title') }} | ||||
|     </BaseHeading> | ||||
|  | ||||
|     <BaseDescriptionList class="mt-5"> | ||||
|       <BaseDescriptionListItem | ||||
|         v-for="(field, index) in customerCustomFields" | ||||
|         :key="index" | ||||
|         :content-loading="contentLoading" | ||||
|         :label="field.custom_field.label" | ||||
|       > | ||||
|         <p | ||||
|           v-if="field.type === 'Switch'" | ||||
|           class="text-sm font-bold leading-5 text-black non-italic" | ||||
|         > | ||||
|           <span v-if="field.default_answer === 1"> Yes </span> | ||||
|           <span v-else> No </span> | ||||
|         </p> | ||||
|         <p v-else class="text-sm font-bold leading-5 text-black non-italic"> | ||||
|           {{ field.default_answer }} | ||||
|         </p> | ||||
|       </BaseDescriptionListItem> | ||||
|     </BaseDescriptionList> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| import { computed } from 'vue' | ||||
| import { useCustomerStore } from '@/scripts/stores/customer' | ||||
|  | ||||
| const customerStore = useCustomerStore() | ||||
|  | ||||
| const selectedViewCustomer = computed(() => customerStore.selectedViewCustomer) | ||||
|  | ||||
| const contentLoading = computed(() => customerStore.isFetchingViewData) | ||||
|  | ||||
| const customerCustomFields = computed(() => { | ||||
|   if (selectedViewCustomer?.value?.fields) { | ||||
|     return selectedViewCustomer?.value?.fields | ||||
|   } | ||||
|   return [] | ||||
| }) | ||||
| </script> | ||||
| @ -0,0 +1,293 @@ | ||||
| <template> | ||||
|   <div | ||||
|     class=" | ||||
|       fixed | ||||
|       top-0 | ||||
|       left-0 | ||||
|       hidden | ||||
|       h-full | ||||
|       pt-16 | ||||
|       pb-4 | ||||
|       ml-56 | ||||
|       bg-white | ||||
|       xl:ml-64 | ||||
|       w-88 | ||||
|       xl:block | ||||
|     " | ||||
|   > | ||||
|     <div | ||||
|       class=" | ||||
|         flex | ||||
|         items-center | ||||
|         justify-between | ||||
|         px-4 | ||||
|         pt-8 | ||||
|         pb-2 | ||||
|         border border-gray-200 border-solid | ||||
|         height-full | ||||
|       " | ||||
|     > | ||||
|       <BaseInput | ||||
|         v-model="searchData.searchText" | ||||
|         :placeholder="$t('general.search')" | ||||
|         container-class="mb-6" | ||||
|         type="text" | ||||
|         variant="gray" | ||||
|         @input="onSearch()" | ||||
|       > | ||||
|         <BaseIcon name="SearchIcon" class="text-gray-500" /> | ||||
|       </BaseInput> | ||||
|  | ||||
|       <div class="flex mb-6 ml-3" role="group" aria-label="First group"> | ||||
|         <BaseDropdown | ||||
|           :close-on-select="false" | ||||
|           position="bottom-start" | ||||
|           width-class="w-40" | ||||
|           position-class="left-0" | ||||
|         > | ||||
|           <template #activator> | ||||
|             <BaseButton variant="gray"> | ||||
|               <BaseIcon name="FilterIcon" /> | ||||
|             </BaseButton> | ||||
|           </template> | ||||
|  | ||||
|           <div | ||||
|             class=" | ||||
|               px-4 | ||||
|               py-3 | ||||
|               pb-2 | ||||
|               mb-2 | ||||
|               text-sm | ||||
|               border-b border-gray-200 border-solid | ||||
|             " | ||||
|           > | ||||
|             {{ $t('general.sort_by') }} | ||||
|           </div> | ||||
|  | ||||
|           <div class="px-2"> | ||||
|             <BaseDropdownItem | ||||
|               class="flex px-1 py-2 mt-1 cursor-pointer hover:rounded-md" | ||||
|             > | ||||
|               <BaseInputGroup class="pt-2 -mt-4"> | ||||
|                 <BaseRadio | ||||
|                   id="filter_create_date" | ||||
|                   v-model="searchData.orderByField" | ||||
|                   :label="$t('customers.create_date')" | ||||
|                   size="sm" | ||||
|                   name="filter" | ||||
|                   value="invoices.created_at" | ||||
|                   @update:modelValue="onSearch" | ||||
|                 /> | ||||
|               </BaseInputGroup> | ||||
|             </BaseDropdownItem> | ||||
|           </div> | ||||
|  | ||||
|           <div class="px-2"> | ||||
|             <BaseDropdownItem class="flex px-1 cursor-pointer hover:rounded-md"> | ||||
|               <BaseInputGroup class="pt-2 -mt-4"> | ||||
|                 <BaseRadio | ||||
|                   id="filter_display_name" | ||||
|                   v-model="searchData.orderByField" | ||||
|                   :label="$t('customers.display_name')" | ||||
|                   size="sm" | ||||
|                   name="filter" | ||||
|                   value="name" | ||||
|                   @update:modelValue="onSearch" | ||||
|                 /> | ||||
|               </BaseInputGroup> | ||||
|             </BaseDropdownItem> | ||||
|           </div> | ||||
|         </BaseDropdown> | ||||
|  | ||||
|         <BaseButton class="ml-1" size="md" variant="gray" @click="sortData"> | ||||
|           <BaseIcon v-if="getOrderBy" name="SortAscendingIcon" /> | ||||
|           <BaseIcon v-else name="SortDescendingIcon" /> | ||||
|         </BaseButton> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <div | ||||
|       class=" | ||||
|         h-full | ||||
|         pb-32 | ||||
|         overflow-y-scroll | ||||
|         border-l border-gray-200 border-solid | ||||
|         sidebar | ||||
|         base-scroll | ||||
|       " | ||||
|     > | ||||
|       <div v-for="(customer, index) in customerStore.customers" :key="index"> | ||||
|         <router-link | ||||
|           v-if="customer && !isFetching" | ||||
|           :id="'customer-' + customer.id" | ||||
|           :to="`/admin/customers/${customer.id}/view`" | ||||
|           :class="[ | ||||
|             'flex justify-between p-4 items-center cursor-pointer hover:bg-gray-100 border-l-4 border-transparent', | ||||
|             { | ||||
|               'bg-gray-100 border-l-4 border-primary-500 border-solid': | ||||
|                 hasActiveUrl(customer.id), | ||||
|             }, | ||||
|           ]" | ||||
|           style="border-top: 1px solid rgba(185, 193, 209, 0.41)" | ||||
|         > | ||||
|           <div> | ||||
|             <div | ||||
|               class=" | ||||
|                 pr-2 | ||||
|                 text-sm | ||||
|                 not-italic | ||||
|                 font-normal | ||||
|                 leading-5 | ||||
|                 text-black | ||||
|                 capitalize | ||||
|                 truncate | ||||
|               " | ||||
|             > | ||||
|               {{ customer.name }} | ||||
|             </div> | ||||
|             <div | ||||
|               v-if="customer.contact_name" | ||||
|               class=" | ||||
|                 mt-1 | ||||
|                 text-xs | ||||
|                 not-italic | ||||
|                 font-medium | ||||
|                 leading-5 | ||||
|                 text-gray-600 | ||||
|               " | ||||
|             > | ||||
|               {{ customer.contact_name }} | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="flex-1 font-bold text-right whitespace-nowrap"> | ||||
|             <BaseFormatMoney | ||||
|               :amount="customer.due_amount" | ||||
|               :currency="customer.currency" | ||||
|             /> | ||||
|           </div> | ||||
|         </router-link> | ||||
|       </div> | ||||
|       <div class="flex justify-center p-4 items-center"> | ||||
|         <LoadingIcon | ||||
|           v-if="isFetching" | ||||
|           class="h-6 m-1 animate-spin text-primary-400" | ||||
|         /> | ||||
|       </div> | ||||
|       <p | ||||
|         v-if="!customerStore.customers.length && !isFetching" | ||||
|         class="flex justify-center px-4 mt-5 text-sm text-gray-600" | ||||
|       > | ||||
|         {{ $t('customers.no_matching_customers') }} | ||||
|       </p> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| import { computed, ref, reactive, watch, inject } from 'vue' | ||||
| import { useI18n } from 'vue-i18n' | ||||
| import { useRoute } from 'vue-router' | ||||
| import { useCustomerStore } from '@/scripts/stores/customer' | ||||
| import LoadingIcon from '@/scripts/components/icons/LoadingIcon.vue' | ||||
| import { debounce } from 'lodash' | ||||
|  | ||||
| const customerStore = useCustomerStore() | ||||
| const title = 'Customer View' | ||||
| const route = useRoute() | ||||
| const { t } = useI18n() | ||||
|  | ||||
| let isSearching = ref(false) | ||||
|  | ||||
| let isFetching = ref(false) | ||||
|  | ||||
| let searchData = reactive({ | ||||
|   orderBy: '', | ||||
|   orderByField: '', | ||||
|   searchText: '', | ||||
| }) | ||||
|  | ||||
| onSearch = debounce(onSearch, 500) | ||||
|  | ||||
| const getOrderBy = computed(() => { | ||||
|   if (searchData.orderBy === 'asc' || searchData.orderBy == null) { | ||||
|     return true | ||||
|   } | ||||
|   return false | ||||
| }) | ||||
|  | ||||
| const getOrderName = computed(() => | ||||
|   getOrderBy.value ? t('general.ascending') : t('general.descending') | ||||
| ) | ||||
|  | ||||
| function hasActiveUrl(id) { | ||||
|   return route.params.id == id | ||||
| } | ||||
|  | ||||
| async function loadCustomers() { | ||||
|   isFetching.value = true | ||||
|  | ||||
|   await customerStore.fetchCustomers({ limit: 'all' }) | ||||
|  | ||||
|   isFetching.value = false | ||||
|  | ||||
|   setTimeout(() => { | ||||
|     scrollToCustomer() | ||||
|   }, 500) | ||||
| } | ||||
|  | ||||
| function scrollToCustomer() { | ||||
|   const el = document.getElementById(`customer-${route.params.id}`) | ||||
|  | ||||
|   if (el) { | ||||
|     el.scrollIntoView({ behavior: 'smooth' }) | ||||
|     el.classList.add('shake') | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function onSearch() { | ||||
|   let data = {} | ||||
|   if ( | ||||
|     searchData.searchText !== '' && | ||||
|     searchData.searchText !== null && | ||||
|     searchData.searchText !== undefined | ||||
|   ) { | ||||
|     data.display_name = searchData.searchText | ||||
|   } | ||||
|  | ||||
|   if (searchData.orderBy !== null && searchData.orderBy !== undefined) { | ||||
|     data.orderBy = searchData.orderBy | ||||
|   } | ||||
|  | ||||
|   if ( | ||||
|     searchData.orderByField !== null && | ||||
|     searchData.orderByField !== undefined | ||||
|   ) { | ||||
|     data.orderByField = searchData.orderByField | ||||
|   } | ||||
|  | ||||
|   isSearching.value = true | ||||
|  | ||||
|   try { | ||||
|     let response = await customerStore.fetchCustomers(data) | ||||
|     isSearching.value = false | ||||
|     if (response.data) { | ||||
|       customerStore.customers = response.data.data | ||||
|     } | ||||
|   } catch (error) { | ||||
|     isSearching.value = false | ||||
|   } | ||||
| } | ||||
|  | ||||
| function sortData() { | ||||
|   if (searchData.orderBy === 'asc') { | ||||
|     searchData.orderBy = 'desc' | ||||
|     onSearch() | ||||
|     return true | ||||
|   } | ||||
|   searchData.orderBy = 'asc' | ||||
|   onSearch() | ||||
|   return true | ||||
| } | ||||
|  | ||||
| loadCustomers() | ||||
| </script> | ||||
		Reference in New Issue
	
	Block a user