port-nimara-client-portal/components/ExpenseCreateModal.vue

271 lines
6.2 KiB
Vue

<template>
<v-dialog
v-model="dialog"
max-width="600"
persistent
scrollable
>
<v-card>
<v-card-title class="d-flex align-center">
<v-icon class="mr-2">mdi-plus</v-icon>
<span>Add New Expense</span>
<v-spacer />
<v-btn
@click="closeModal"
icon="mdi-close"
variant="text"
size="small"
/>
</v-card-title>
<v-card-text>
<v-form ref="form" @submit.prevent="handleSubmit">
<v-row>
<!-- Establishment Name -->
<v-col cols="12">
<v-text-field
v-model="expense.establishmentName"
label="Establishment Name"
variant="outlined"
:rules="[rules.required]"
required
placeholder="e.g., Shell, American Airlines, etc."
/>
</v-col>
<!-- Price -->
<v-col cols="12" sm="6">
<v-text-field
v-model="expense.price"
label="Price"
variant="outlined"
:rules="[rules.required, rules.price]"
required
placeholder="e.g., 59.95"
prepend-inner-icon="mdi-currency-eur"
/>
</v-col>
<!-- Category -->
<v-col cols="12" sm="6">
<v-select
v-model="expense.category"
:items="categories"
label="Category"
variant="outlined"
:rules="[rules.required]"
required
/>
</v-col>
<!-- Payer -->
<v-col cols="12" sm="6">
<v-text-field
v-model="expense.payer"
label="Payer"
variant="outlined"
:rules="[rules.required]"
required
placeholder="e.g., John, Mary, etc."
/>
</v-col>
<!-- Payment Method -->
<v-col cols="12" sm="6">
<v-select
v-model="expense.paymentMethod"
:items="paymentMethods"
label="Payment Method"
variant="outlined"
:rules="[rules.required]"
required
/>
</v-col>
<!-- Date -->
<v-col cols="12">
<v-text-field
v-model="expense.date"
type="date"
label="Date"
variant="outlined"
:rules="[rules.required]"
required
/>
</v-col>
<!-- Contents/Description -->
<v-col cols="12">
<v-textarea
v-model="expense.contents"
label="Description (optional)"
variant="outlined"
rows="3"
placeholder="Additional details about the expense..."
/>
</v-col>
</v-row>
</v-form>
</v-card-text>
<v-card-actions class="px-6 pb-4">
<v-spacer />
<v-btn
@click="closeModal"
variant="text"
:disabled="creating"
>
Cancel
</v-btn>
<v-btn
@click="handleSubmit"
:disabled="creating"
color="primary"
:loading="creating"
>
<v-icon v-if="!creating" class="mr-1">mdi-plus</v-icon>
Create Expense
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
// Props
interface Props {
modelValue: boolean;
}
const props = defineProps<Props>();
// Emits
const emit = defineEmits<{
'update:modelValue': [value: boolean];
'created': [expense: any];
}>();
// Computed dialog model
const dialog = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
});
// Reactive state
const form = ref();
const creating = ref(false);
const expense = ref({
establishmentName: '',
price: '',
category: '',
payer: '',
paymentMethod: '',
date: '',
contents: ''
});
// Form options
const categories = [
'Food/Drinks',
'Shop',
'Online',
'Other'
];
const paymentMethods = [
'Card',
'Cash',
'N/A'
];
// Validation rules
const rules = {
required: (value: string) => !!value || 'This field is required',
price: (value: string) => {
if (!value) return 'Price is required';
const num = parseFloat(value);
if (isNaN(num) || num <= 0) return 'Please enter a valid price';
return true;
}
};
// Methods
const closeModal = () => {
dialog.value = false;
resetForm();
};
const resetForm = () => {
expense.value = {
establishmentName: '',
price: '',
category: '',
payer: '',
paymentMethod: '',
date: '',
contents: ''
};
if (form.value) {
form.value.resetValidation();
}
};
const handleSubmit = async () => {
if (!form.value) return;
const { valid } = await form.value.validate();
if (!valid) return;
creating.value = true;
try {
// Create expense via API
const response = await $fetch<{
success: boolean;
data?: any;
message?: string;
}>('/api/create-expense', {
method: 'POST',
body: {
'Establishment Name': expense.value.establishmentName,
'Price': expense.value.price,
'Category': expense.value.category,
'Payer': expense.value.payer,
'Payment Method': expense.value.paymentMethod,
'Time': expense.value.date,
'Contents': expense.value.contents
}
});
if (response.success) {
emit('created', response.data);
closeModal();
}
} catch (error: any) {
console.error('[ExpenseCreateModal] Error creating expense:', error);
// Handle error display here if needed
} finally {
creating.value = false;
}
};
// Watch for modal open to set default date
watch(dialog, (isOpen) => {
if (isOpen && !expense.value.date) {
expense.value.date = new Date().toISOString().slice(0, 10);
}
});
</script>
<style scoped>
.v-card-actions {
border-top: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
}
</style>