v6 update

This commit is contained in:
Mohit Panjwani
2022-01-10 16:06:17 +05:30
parent b770e6277f
commit bdea879273
722 changed files with 19047 additions and 9186 deletions

View 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/admin/components/charts/LineChart.vue'
import { ref, computed, watch, reactive, inject } from 'vue'
import { useCustomerStore } from '@/scripts/admin/stores/customer'
import { useRoute } from 'vue-router'
import { useCompanyStore } from '@/scripts/admin/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>

View File

@ -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>

View 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/admin/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>

View File

@ -0,0 +1,294 @@
<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>
<BaseText
:text="customer.name"
:length="30"
class="
pr-2
text-sm
not-italic
font-normal
leading-5
text-black
capitalize
truncate
"
/>
<BaseText
v-if="customer.contact_name"
:text="customer.contact_name"
:length="30"
class="
mt-1
text-xs
not-italic
font-medium
leading-5
text-gray-600
"
/>
</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/admin/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>