mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-17 10:14:08 +00:00
build passes Create all missing components (modals, dropdowns, icons, tabs, mail drivers, customer partials), fix all @/scripts/ imports to @v2/, wire up vite entry point and blade template. 382 files, 48883 lines. - 27 settings components: modals (tax, payment, custom field, note, category, role, exchange rate, unit, mail test), dropdowns (6), customization tabs (4), mail driver forms (4) - 22 icon components: 5 utility icons, 4 dashboard icons, 13 editor toolbar icons with typed barrel export - 3 customer components: info, chart placeholder, custom fields single - Fixed usePopper composable, client/format-money import patterns - Zero remaining @/scripts/ imports in scripts-v2/ Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
209 lines
5.9 KiB
Vue
209 lines
5.9 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, watch, reactive } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useCustomerStore } from '../store'
|
|
import { useCompanyStore } from '@v2/stores/company.store'
|
|
import { customerService } from '@v2/api/services/customer.service'
|
|
import LineChart from '@v2/components/charts/LineChart.vue'
|
|
import ChartPlaceholder from './CustomerChartPlaceholder.vue'
|
|
import CustomerInfo from './CustomerInfo.vue'
|
|
|
|
interface ChartData {
|
|
salesTotal: number
|
|
totalReceipts: number
|
|
totalExpenses: number
|
|
netProfit: number
|
|
expenseTotals: number[]
|
|
netProfits: number[]
|
|
months: string[]
|
|
receiptTotals: number[]
|
|
invoiceTotals: number[]
|
|
}
|
|
|
|
interface YearOption {
|
|
label: string
|
|
value: string
|
|
}
|
|
|
|
const companyStore = useCompanyStore()
|
|
const customerStore = useCustomerStore()
|
|
const { t } = useI18n()
|
|
const route = useRoute()
|
|
|
|
const isLoading = ref<boolean>(false)
|
|
const chartData = reactive<Partial<ChartData>>({})
|
|
const years = reactive<YearOption[]>([
|
|
{ label: t('dateRange.this_year'), value: 'This year' },
|
|
{ label: t('dateRange.previous_year'), value: 'Previous year' },
|
|
])
|
|
const selectedYear = ref<string>('This year')
|
|
|
|
const getChartExpenses = computed<number[]>(() => chartData.expenseTotals ?? [])
|
|
const getNetProfits = computed<number[]>(() => chartData.netProfits ?? [])
|
|
const getChartMonths = computed<string[]>(() => chartData.months ?? [])
|
|
const getReceiptTotals = computed<number[]>(() => chartData.receiptTotals ?? [])
|
|
const getChartInvoices = computed<number[]>(() => chartData.invoiceTotals ?? [])
|
|
|
|
watch(
|
|
route,
|
|
() => {
|
|
if (route.params.id) {
|
|
loadCustomer()
|
|
}
|
|
selectedYear.value = 'This year'
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
async function loadCustomer(): Promise<void> {
|
|
isLoading.value = false
|
|
const response = await customerService.getStats(Number(route.params.id))
|
|
|
|
if (response.data) {
|
|
const meta = (response as Record<string, unknown>).meta as Record<string, unknown> | undefined
|
|
if (meta?.chartData) {
|
|
Object.assign(chartData, meta.chartData)
|
|
}
|
|
}
|
|
|
|
isLoading.value = true
|
|
}
|
|
|
|
async function onChangeYear(data: string): Promise<boolean> {
|
|
const params: Record<string, unknown> = {
|
|
id: route.params.id,
|
|
}
|
|
|
|
if (data === 'Previous year') {
|
|
params.previous_year = true
|
|
} else {
|
|
params.this_year = true
|
|
}
|
|
|
|
const response = await customerService.getStats(
|
|
Number(route.params.id),
|
|
params
|
|
)
|
|
|
|
const meta = (response as Record<string, unknown>).meta as Record<string, unknown> | undefined
|
|
if (meta?.chartData) {
|
|
Object.assign(chartData, meta.chartData)
|
|
}
|
|
|
|
return true
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<BaseCard class="flex flex-col mt-6">
|
|
<ChartPlaceholder v-if="!isLoading" />
|
|
|
|
<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="ChartBarSquareIcon" 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="companyStore.selectedCompanyCurrency"
|
|
/>
|
|
</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.totalReceipts"
|
|
:currency="companyStore.selectedCompanyCurrency"
|
|
/>
|
|
</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="companyStore.selectedCompanyCurrency"
|
|
/>
|
|
</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="companyStore.selectedCompanyCurrency"
|
|
/>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<CustomerInfo />
|
|
</BaseCard>
|
|
</template>
|