Files
InvoiceShelf/resources/scripts-v2/features/company/customers/components/CustomerChart.vue
Darko Gjorgjijoski a46cca5cd8 Complete scripts-v2 TypeScript migration — all imports resolved,
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>
2026-04-04 09:30:00 +02:00

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>