mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-15 09:14:08 +00:00
Now that the legacy v1 frontend (commit 064bdf53) is gone, the v2 directory is the only frontend and the v2 suffix is just noise. Renames resources/scripts-v2 to resources/scripts via git mv (so git records the move as renames, preserving blame and log --follow), then bulk-rewrites the 152 files that imported via @v2/... to use @/scripts/... instead. The existing @ alias (resources/) covers the new path with no extra config needed.
Drops the now-unused @v2 alias from vite.config.js and points the laravel-vite-plugin entry at resources/scripts/main.ts. Updates the only blade reference (resources/views/app.blade.php) to match. The package.json test script (eslint ./resources/scripts) automatically targets the right place after the rename without any edit.
Verified: npm run build exits clean and the Vite warning lines now reference resources/scripts/plugins/i18n.ts, confirming every import resolved through the new path. git log --follow on any moved file walks back through its scripts-v2 history.
193 lines
5.0 KiB
Vue
193 lines
5.0 KiB
Vue
<template>
|
|
<BaseInputGroup
|
|
v-if="showExchangeRate && selectedCurrency"
|
|
:content-loading="isFetching && !isEdit"
|
|
:label="$t('settings.exchange_rate.exchange_rate')"
|
|
:error="
|
|
v.exchange_rate.$error && v.exchange_rate.$errors[0].$message
|
|
"
|
|
required
|
|
>
|
|
<template #labelRight>
|
|
<div v-if="hasActiveProvider && isEdit">
|
|
<BaseIcon
|
|
v-tooltip="{ content: 'Fetch Latest Exchange rate' }"
|
|
name="ArrowPathIcon"
|
|
:class="`h-4 w-4 text-primary-500 cursor-pointer outline-hidden ${
|
|
isFetching
|
|
? ' animate-spin rotate-180 cursor-not-allowed pointer-events-none '
|
|
: ''
|
|
}`"
|
|
@click="getCurrentExchangeRate(customerCurrency)"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<BaseInput
|
|
v-model="exchangeRate"
|
|
:content-loading="isFetching && !isEdit"
|
|
:addon="`1 ${selectedCurrency.code} =`"
|
|
:disabled="isFetching"
|
|
@input="v.exchange_rate.$touch()"
|
|
>
|
|
<template #right>
|
|
<span class="text-muted sm:text-sm">
|
|
{{ companyCurrency?.code ?? '' }}
|
|
</span>
|
|
</template>
|
|
</BaseInput>
|
|
|
|
<span class="text-subtle text-xs mt-2 font-light">
|
|
{{
|
|
$t('settings.exchange_rate.exchange_help_text', {
|
|
currency: selectedCurrency.code,
|
|
baseCurrency: companyCurrency?.code ?? '',
|
|
})
|
|
}}
|
|
</span>
|
|
</BaseInputGroup>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { watch, computed, ref, onMounted, onBeforeUnmount } from 'vue'
|
|
import { exchangeRateService } from '../../../api/services/exchange-rate.service'
|
|
import { useCompanyStore } from '../../../stores/company.store'
|
|
import { useGlobalStore } from '../../../stores/global.store'
|
|
import type { Currency } from '../../../types/domain/currency'
|
|
import type { DocumentFormData } from './use-document-calculations'
|
|
|
|
interface Props {
|
|
v: Record<string, { $error: boolean; $errors: Array<{ $message: string }>; $touch: () => void }>
|
|
isLoading?: boolean
|
|
store: Record<string, unknown> & {
|
|
showExchangeRate: boolean
|
|
}
|
|
storeProp: string
|
|
isEdit?: boolean
|
|
customerCurrency?: number | string | null
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
isLoading: false,
|
|
isEdit: false,
|
|
customerCurrency: null,
|
|
})
|
|
|
|
const companyStore = useCompanyStore()
|
|
const globalStore = useGlobalStore()
|
|
|
|
const hasActiveProvider = ref<boolean>(false)
|
|
const isFetching = ref<boolean>(false)
|
|
|
|
onMounted(() => {
|
|
globalStore.fetchCurrencies()
|
|
})
|
|
|
|
const formData = computed<DocumentFormData>(() => {
|
|
return props.store[props.storeProp] as DocumentFormData
|
|
})
|
|
|
|
const showExchangeRate = computed<boolean>(() => {
|
|
return props.store.showExchangeRate
|
|
})
|
|
|
|
const exchangeRate = computed<number | null | string>({
|
|
get: () => formData.value.exchange_rate ?? null,
|
|
set: (value) => {
|
|
formData.value.exchange_rate = value as number
|
|
},
|
|
})
|
|
|
|
const companyCurrency = computed<Currency | null>(() => {
|
|
return companyStore.selectedCompanyCurrency
|
|
})
|
|
|
|
const selectedCurrency = computed<Currency | null>(() => {
|
|
return (
|
|
globalStore.currencies.find((c: Currency) => c.id === formData.value.currency_id) ?? null
|
|
)
|
|
})
|
|
|
|
const isCurrencyDifferent = computed<boolean>(() => {
|
|
return companyCurrency.value?.id !== props.customerCurrency
|
|
})
|
|
|
|
watch(
|
|
() => (formData.value as Record<string, unknown>).customer,
|
|
(v) => {
|
|
setCustomerCurrency(v as Record<string, unknown> | null)
|
|
},
|
|
{ deep: true },
|
|
)
|
|
|
|
watch(
|
|
() => formData.value.currency_id,
|
|
(v) => {
|
|
onChangeCurrency(v)
|
|
},
|
|
{ immediate: true },
|
|
)
|
|
|
|
watch(
|
|
() => props.customerCurrency,
|
|
(v) => {
|
|
if (v && props.isEdit) {
|
|
checkForActiveProvider()
|
|
}
|
|
},
|
|
{ immediate: true },
|
|
)
|
|
|
|
function checkForActiveProvider(): void {
|
|
if (isCurrencyDifferent.value && props.customerCurrency) {
|
|
exchangeRateService
|
|
.getActiveProvider(Number(props.customerCurrency))
|
|
.then((res) => {
|
|
hasActiveProvider.value = res.hasActiveProvider
|
|
})
|
|
.catch(() => {
|
|
hasActiveProvider.value = false
|
|
})
|
|
}
|
|
}
|
|
|
|
function setCustomerCurrency(v: Record<string, unknown> | null): void {
|
|
if (v) {
|
|
const currency = v.currency as Currency | undefined
|
|
if (currency) {
|
|
formData.value.currency_id = currency.id
|
|
}
|
|
} else if (companyCurrency.value) {
|
|
formData.value.currency_id = companyCurrency.value.id
|
|
}
|
|
}
|
|
|
|
async function onChangeCurrency(v: number | undefined): Promise<void> {
|
|
if (v !== companyCurrency.value?.id) {
|
|
if (!props.isEdit && v) {
|
|
await getCurrentExchangeRate(v)
|
|
}
|
|
props.store.showExchangeRate = true
|
|
} else {
|
|
props.store.showExchangeRate = false
|
|
}
|
|
}
|
|
|
|
async function getCurrentExchangeRate(v: number | string | null | undefined): Promise<void> {
|
|
if (!v) return
|
|
isFetching.value = true
|
|
try {
|
|
const res = await exchangeRateService.getRate(Number(v))
|
|
formData.value.exchange_rate = res.exchangeRate
|
|
} catch {
|
|
// Silently fail
|
|
} finally {
|
|
isFetching.value = false
|
|
}
|
|
}
|
|
|
|
onBeforeUnmount(() => {
|
|
props.store.showExchangeRate = false
|
|
})
|
|
</script>
|