init
This commit is contained in:
324
src/components/budgets/TransactionEditDrawer.vue
Normal file
324
src/components/budgets/TransactionEditDrawer.vue
Normal file
@@ -0,0 +1,324 @@
|
||||
<script setup lang="ts">
|
||||
import Drawer from "primevue/drawer";
|
||||
import InputText from "primevue/inputtext";
|
||||
import DatePicker from "primevue/datepicker";
|
||||
import FloatLabel from "primevue/floatlabel";
|
||||
import InputNumber from "primevue/inputnumber";
|
||||
import Button from "primevue/button";
|
||||
import {ref, onMounted, computed} from 'vue';
|
||||
import {Transaction, TransactionType} from "@/models/Transaction";
|
||||
import {CategoryType} from "@/models/Category";
|
||||
import SelectButton from "primevue/selectbutton";
|
||||
import {
|
||||
createTransactionRequest,
|
||||
getTransactionTypes,
|
||||
updateTransactionRequest,
|
||||
deleteTransactionRequest
|
||||
} from "@/services/transactionService";
|
||||
import {getCategories, getCategoryTypes} from "@/services/categoryService";
|
||||
import {useToast} from "primevue/usetoast";
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
transaction: {
|
||||
type: Object as () => Transaction,
|
||||
required: false
|
||||
},
|
||||
transactionType: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
categoryType: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['create-transaction', 'update-transaction', 'delete-transaction', 'close-drawer']);
|
||||
const toast = useToast();
|
||||
const categoryTypeChanged = () => {
|
||||
console.log(selectedCategoryType.value)
|
||||
editedTransaction.value.category = selectedCategoryType.value.code == "EXPENSE" ? expenseCategories.value[0] : incomeCategories.value[0];
|
||||
|
||||
}
|
||||
const selectCategory = (category) => {
|
||||
isCategorySelectorOpened.value = false;
|
||||
editedTransaction.value.category = category;
|
||||
};
|
||||
|
||||
|
||||
// Состояние
|
||||
const loading = ref(false);
|
||||
const isEditing = ref(!!props.transaction);
|
||||
const isCategorySelectorOpened = ref(false);
|
||||
const editedTransaction = ref<Transaction | null>(null);
|
||||
|
||||
const selectedCategoryType = ref<CategoryType | null>(null);
|
||||
const selectedTransactionType = ref<TransactionType | null>(null);
|
||||
|
||||
const entireCategories = ref<Category[]>([]);
|
||||
const expenseCategories = ref<Category[]>([]);
|
||||
const incomeCategories = ref<Category[]>([]);
|
||||
const categoryTypes = ref<CategoryType[]>([]);
|
||||
const transactionTypes = ref<TransactionType[]>([]);
|
||||
|
||||
// Получение категорий и типов транзакций
|
||||
const fetchCategoriesAndTypes = async () => {
|
||||
try {
|
||||
const [categoriesResponse, categoryTypesResponse, transactionTypesResponse] = await Promise.all([
|
||||
getCategories(),
|
||||
getCategoryTypes(),
|
||||
getTransactionTypes()
|
||||
]);
|
||||
entireCategories.value = categoriesResponse.data;
|
||||
expenseCategories.value = categoriesResponse.data.filter((category: Category) => category.type.code === 'EXPENSE');
|
||||
incomeCategories.value = categoriesResponse.data.filter((category: Category) => category.type.code === 'INCOME');
|
||||
|
||||
categoryTypes.value = categoryTypesResponse.data;
|
||||
transactionTypes.value = transactionTypesResponse.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching categories and types:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Инициализация данных
|
||||
const prepareData = () => {
|
||||
if (!props.transaction) {
|
||||
editedTransaction.value = new Transaction();
|
||||
editedTransaction.value.transactionType = transactionTypes.value.find(type => type.code === props.transactionType) || transactionTypes.value[0];
|
||||
selectedCategoryType.value = categoryTypes.value.find(type => type.code === props.categoryType) || categoryTypes.value[0];
|
||||
editedTransaction.value.category = props.categoryType === 'EXPENSE' ? expenseCategories.value[0] : incomeCategories.value[0];
|
||||
editedTransaction.value.date = new Date();
|
||||
} else {
|
||||
editedTransaction.value = {...props.transaction};
|
||||
selectedCategoryType.value = editedTransaction.value.category.type;
|
||||
selectedTransactionType.value = editedTransaction.value.transactionType;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Создание транзакции
|
||||
const createTransaction = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
if (editedTransaction.value.transactionType.code === 'INSTANT') {
|
||||
editedTransaction.value.isDone = true;
|
||||
}
|
||||
await createTransactionRequest(editedTransaction.value);
|
||||
toast.add({severity: 'success', summary: 'Transaction created!', detail: 'Транзакция создана!', life: 3000});
|
||||
emit('create-transaction', editedTransaction.value);
|
||||
resetForm();
|
||||
} catch (error) {
|
||||
console.error('Error creating transaction:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
console.log(editedTransaction.value)
|
||||
}
|
||||
};
|
||||
|
||||
// Обновление транзакции
|
||||
const updateTransaction = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await updateTransactionRequest(editedTransaction.value);
|
||||
editedTransaction.value = response.data;
|
||||
toast.add({severity: 'success', summary: 'Transaction updated!', detail: 'Транзакция обновлена!', life: 3000});
|
||||
emit('update-transaction', editedTransaction.value);
|
||||
} catch (error) {
|
||||
console.error('Error updating transaction:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Удаление транзакции
|
||||
const deleteTransaction = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
await deleteTransactionRequest(editedTransaction.value.id);
|
||||
toast.add({severity: 'success', summary: 'Transaction deleted!', detail: 'Транзакция удалена!', life: 3000});
|
||||
emit('delete-transaction', editedTransaction.value);
|
||||
closeDrawer()
|
||||
} catch (error) {
|
||||
console.error('Error deleting transaction:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Сброс формы
|
||||
const resetForm = () => {
|
||||
|
||||
editedTransaction.value.date = new Date();
|
||||
editedTransaction.value.amount = null;
|
||||
editedTransaction.value.comment = '';
|
||||
|
||||
};
|
||||
|
||||
const dateErrorMessage = computed(() => {
|
||||
console.log('tut')
|
||||
if (editedTransaction.value.transactionType.code != 'PLANNED' && editedTransaction.value.date > new Date()) {
|
||||
console.log('tut2')
|
||||
return 'При мгновенных тратах дата должна быть меньше текущей!'
|
||||
} else if (editedTransaction.value.transactionType.code == 'PLANNED' && editedTransaction.value.date < new Date()){
|
||||
console.log('tu3')
|
||||
return 'При плановых тратах дата должна быть больше текущей!'
|
||||
}
|
||||
else {
|
||||
console.log('tu4')
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
// Закрытие окна
|
||||
const closeDrawer = () => emit('close-drawer');
|
||||
|
||||
// Мониторинг при монтировании
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
await fetchCategoriesAndTypes();
|
||||
prepareData();
|
||||
loading.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div class="card flex justify-center">
|
||||
|
||||
<Drawer :visible="visible" :header="isEditing ? 'Edit Transaction' : 'Create Transaction'" :showCloseIcon="false"
|
||||
position="right" @hide="closeDrawer"
|
||||
class="!w-128 ">
|
||||
<div v-if="loading">
|
||||
Loading...
|
||||
</div>
|
||||
<div v-else class="p-fluid grid formgrid p-4 w-full gap-5">
|
||||
|
||||
<div class="relative w-full justify-center justify-items-center ">
|
||||
<div class="flex flex-col justify-items-center gap-2">
|
||||
|
||||
<!-- {{editedTransaction.value.transactionType}}-->
|
||||
<SelectButton v-if="!isEditing" v-model="editedTransaction.transactionType" :allow-empty="false"
|
||||
:options="transactionTypes"
|
||||
optionLabel="name"
|
||||
aria-labelledby="basic"
|
||||
class="justify-center"/>
|
||||
<SelectButton v-model="selectedCategoryType" :options="categoryTypes" :allow-empty="false"
|
||||
optionLabel="name"
|
||||
aria-labelledby="basic"
|
||||
@change="categoryTypeChanged" class="justify-center"/>
|
||||
<button class="border border-gray-300 rounded-lg w-full z-50"
|
||||
@click="isCategorySelectorOpened = !isCategorySelectorOpened">
|
||||
<div class="flex flex-row items-center pe-4 py-2 ">
|
||||
<div class="flex flex-row justify-between w-full gap-4 px-4 items-center">
|
||||
<p class="text-3xl font-bold text-gray-700 dark:text-gray-400">{{
|
||||
editedTransaction.category.icon
|
||||
}}</p>
|
||||
<div class="flex flex-col items-start justify-items-start justify-around w-full">
|
||||
<p class="font-bold text-start">{{ editedTransaction.category.name }}</p>
|
||||
<p class="font-light line-clamp-1 items-start text-start">{{
|
||||
editedTransaction.category.description
|
||||
}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span :class="{'rotate-90': isCategorySelectorOpened}"
|
||||
class="pi pi-angle-right transition-transform duration-300 text-5xl"/>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Анимированное открытие списка категорий -->
|
||||
<div v-show="isCategorySelectorOpened"
|
||||
class="absolute left-0 right-0 top-full overflow-hidden z-50 border-b-4 border-x rounded-b-lg bg-white shadow-lg transition-all duration-500"
|
||||
:class="{ 'max-h-0': !isCategorySelectorOpened, 'max-h-[500px]': isCategorySelectorOpened }">
|
||||
<div class="grid grid-cols-2 mt-2">
|
||||
<button
|
||||
v-for="category in editedTransaction.category.type.code == 'EXPENSE' ? expenseCategories : incomeCategories"
|
||||
:key="category.id" class="border rounded-lg mx-2 mb-2"
|
||||
@click="selectCategory(category)">
|
||||
<div class="flex flex-row justify-between w-full px-2">
|
||||
<p class="text-4xl font-bold text-gray-700 dark:text-gray-400">{{ category.icon }}</p>
|
||||
<div class="flex flex-col items-start justify-items-start justify-around w-full">
|
||||
<p class="font-bold text-start">{{ category.name }}</p>
|
||||
<p class="font-light line-clamp-1 text-start">{{ category.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Comment Input -->
|
||||
<div class="field col-12 w-full">
|
||||
<FloatLabel variant="on" class="w-full">
|
||||
<label for="comment">Comment</label>
|
||||
<InputText class="w-full"
|
||||
:invalid="!editedTransaction.comment"
|
||||
id="comment"
|
||||
v-model="editedTransaction.comment"
|
||||
|
||||
/>
|
||||
</FloatLabel>
|
||||
</div>
|
||||
|
||||
<!-- Date Picker -->
|
||||
<div class="field col-12 gap-0">
|
||||
<FloatLabel variant="on">
|
||||
<label for="date">Date </label>
|
||||
|
||||
<DatePicker class="w-full"
|
||||
inline
|
||||
:invalid="editedTransaction.transactionType.code != 'PLANNED' ? editedTransaction.date > new Date() : true"
|
||||
id="date"
|
||||
v-model="editedTransaction.date"
|
||||
dateFormat="yy-mm-dd"
|
||||
showIcon
|
||||
/>
|
||||
<p :class="dateErrorMessage != '' ? 'visible' : 'invisible'"
|
||||
class="text-red-400">{{dateErrorMessage}}</p>
|
||||
|
||||
|
||||
</FloatLabel>
|
||||
</div>
|
||||
|
||||
<!-- Amount Input -->
|
||||
<div class="field col-12">
|
||||
<FloatLabel variant="on">
|
||||
<InputNumber class="w-full"
|
||||
:invalid="!editedTransaction.amount"
|
||||
:minFractionDigits="0"
|
||||
id="amount"
|
||||
v-model="editedTransaction.amount"
|
||||
mode="currency"
|
||||
currency="RUB"
|
||||
locale="ru-RU"
|
||||
|
||||
/>
|
||||
<label for="amount">Amount</label>
|
||||
</FloatLabel>
|
||||
</div>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="field col-12 flex justify-content-end gap-4">
|
||||
|
||||
<Button label="Save" icon="pi pi-check" class="p-button-success"
|
||||
@click="isEditing ? updateTransaction() : createTransaction()"/>
|
||||
<Button label="Cancel" icon="pi pi-times" class="p-button-secondary " @click="closeDrawer"/>
|
||||
<Button v-if="isEditing" label="Delete" icon="pi pi-times" class="p-button-success" severity="danger"
|
||||
@click="deleteTransaction"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.formgrid .field {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user