Enables currency selection for expenses
Allows users to select a currency when adding an expense. Fetches available currencies from the backend and displays them in a dropdown menu, defaulting to EUR if the fetch fails. The expense submission process now includes the selected currency.
This commit is contained in:
parent
9c8b696f06
commit
8dad92f0e5
2 changed files with 82 additions and 5 deletions
|
|
@ -80,7 +80,7 @@
|
||||||
<!-- Amount -->
|
<!-- Amount -->
|
||||||
<FormField v-slot="{ componentField }" name="amount">
|
<FormField v-slot="{ componentField }" name="amount">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Amount (EUR) *</FormLabel>
|
<FormLabel>Amount *</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -91,7 +91,34 @@
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Amount in Euros
|
Amount in selected currency
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<!-- Currency -->
|
||||||
|
<FormField v-slot="{ componentField }" name="currency">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Currency *</FormLabel>
|
||||||
|
<Select v-bind="componentField">
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select currency" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem
|
||||||
|
v-for="currency in availableCurrencies"
|
||||||
|
:key="currency"
|
||||||
|
:value="currency"
|
||||||
|
>
|
||||||
|
{{ currency }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormDescription>
|
||||||
|
Currency for this expense
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
@ -179,7 +206,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -195,6 +222,13 @@ import {
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/components/ui/form'
|
} from '@/components/ui/form'
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select'
|
||||||
import { DollarSign, X, ChevronLeft, Loader2 } from 'lucide-vue-next'
|
import { DollarSign, X, ChevronLeft, Loader2 } from 'lucide-vue-next'
|
||||||
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||||||
import { useAuth } from '@/composables/useAuthService'
|
import { useAuth } from '@/composables/useAuthService'
|
||||||
|
|
@ -220,12 +254,15 @@ const toast = useToast()
|
||||||
const currentStep = ref(1)
|
const currentStep = ref(1)
|
||||||
const selectedAccount = ref<Account | null>(null)
|
const selectedAccount = ref<Account | null>(null)
|
||||||
const isSubmitting = ref(false)
|
const isSubmitting = ref(false)
|
||||||
|
const availableCurrencies = ref<string[]>([])
|
||||||
|
const loadingCurrencies = ref(true)
|
||||||
|
|
||||||
// Form schema
|
// Form schema
|
||||||
const formSchema = toTypedSchema(
|
const formSchema = toTypedSchema(
|
||||||
z.object({
|
z.object({
|
||||||
description: z.string().min(1, 'Description is required').max(500, 'Description too long'),
|
description: z.string().min(1, 'Description is required').max(500, 'Description too long'),
|
||||||
amount: z.coerce.number().min(0.01, 'Amount must be at least €0.01'),
|
amount: z.coerce.number().min(0.01, 'Amount must be at least 0.01'),
|
||||||
|
currency: z.string().min(1, 'Currency is required'),
|
||||||
reference: z.string().max(100, 'Reference too long').optional(),
|
reference: z.string().max(100, 'Reference too long').optional(),
|
||||||
isEquity: z.boolean().default(false)
|
isEquity: z.boolean().default(false)
|
||||||
})
|
})
|
||||||
|
|
@ -237,6 +274,7 @@ const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
description: '',
|
description: '',
|
||||||
amount: 0,
|
amount: 0,
|
||||||
|
currency: 'EUR',
|
||||||
reference: '',
|
reference: '',
|
||||||
isEquity: false
|
isEquity: false
|
||||||
}
|
}
|
||||||
|
|
@ -245,6 +283,24 @@ const form = useForm({
|
||||||
const { resetForm, meta } = form
|
const { resetForm, meta } = form
|
||||||
const isFormValid = computed(() => meta.value.valid)
|
const isFormValid = computed(() => meta.value.valid)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch available currencies on component mount
|
||||||
|
*/
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
loadingCurrencies.value = true
|
||||||
|
const currencies = await expensesAPI.getCurrencies()
|
||||||
|
availableCurrencies.value = currencies
|
||||||
|
console.log('[AddExpense] Loaded currencies:', currencies)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[AddExpense] Failed to load currencies:', error)
|
||||||
|
toast.error('Failed to load currencies', { description: 'Please check your connection and try again' })
|
||||||
|
availableCurrencies.value = []
|
||||||
|
} finally {
|
||||||
|
loadingCurrencies.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle account selection
|
* Handle account selection
|
||||||
*/
|
*/
|
||||||
|
|
@ -280,7 +336,7 @@ const onSubmit = form.handleSubmit(async (values) => {
|
||||||
is_equity: values.isEquity,
|
is_equity: values.isEquity,
|
||||||
user_wallet: wallet.id,
|
user_wallet: wallet.id,
|
||||||
reference: values.reference,
|
reference: values.reference,
|
||||||
currency: 'EUR'
|
currency: values.currency
|
||||||
})
|
})
|
||||||
|
|
||||||
// Show success message
|
// Show success message
|
||||||
|
|
|
||||||
|
|
@ -207,4 +207,25 @@ export class ExpensesAPI extends BaseService {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get available currencies from LNbits instance
|
||||||
|
*/
|
||||||
|
async getCurrencies(): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${this.baseUrl}/api/v1/currencies`, {
|
||||||
|
method: 'GET',
|
||||||
|
signal: AbortSignal.timeout(this.config?.apiConfig?.timeout || 30000)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch currencies: ${response.statusText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[ExpensesAPI] Error fetching currencies:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue