Files
luminic-front/src/components/budgets/TransactionEditDrawer.vue
Vladimir Voronin f1573b9e30 chet novoe
2024-10-25 21:05:18 +03:00

328 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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 Select from "primevue/select";
import {
createTransactionRequest,
getTransactionTypes,
updateTransactionRequest,
deleteTransactionRequest
} from "@/services/transactionService";
import {getCategories, getCategoryTypes} from "@/services/categoryService";
import {useToast} from "primevue/usetoast";
import LoadingView from "@/components/LoadingView.vue";
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 h-dvh">
<Drawer :visible="visible" :header="isEditing ? 'Edit Transaction' : 'Create Transaction'" :showCloseIcon="false"
position="right" @hide="closeDrawer"
class="!w-128 ">
<LoadingView v-if="loading"/>
<div v-else class=" grid gap-4 w-full ">
<div class="relative w-full justify-center justify-items-center ">
<div class="flex flex-col justify-items-center gap-2">
<div class="flex flex-row gap-2">
<!-- {{editedTransaction.value.transactionType}}-->
<Select 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"/>
</div>
<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>
<div class="flex flex-row gap-4">
<FloatLabel variant="on" class="">
<InputNumber class=""
:invalid="!editedTransaction.amount"
:minFractionDigits="0"
id="amount"
v-model="editedTransaction.amount"
mode="currency"
currency="RUB"
locale="ru-RU"
/>
<label for="amount" class="">Amount</label>
</FloatLabel>
<!-- Comment Input -->
<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 -->
<!-- Buttons -->
<div class="fixed col-12 bottom-6 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>
</style>