fixes
This commit is contained in:
19
src/components/DrawerForm.vue
Normal file
19
src/components/DrawerForm.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<Drawer :visible="visible" :header="isEditing ? 'Изменить транзакцию ' : 'Создать транзакцию'" :showCloseIcon="false"
|
||||
position="right" @hide="closeDrawer"
|
||||
class="!w-128 hidden lg:block ">
|
||||
<slot />
|
||||
</Drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Drawer from "primevue/drawer";
|
||||
const props = defineProps({
|
||||
visible: Boolean,
|
||||
isEditing: Boolean,
|
||||
});
|
||||
const emits = defineEmits(['close-drawer']);
|
||||
function closeDrawer() {
|
||||
emits('close-drawer');
|
||||
}
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
<template>
|
||||
<div class="card ">
|
||||
<div v-if="!loadingUser" class="card ">
|
||||
<Menubar :model="items" >
|
||||
<template #start>
|
||||
<!-- <svg width="35" height="40" viewBox="0 0 35 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="h-8">-->
|
||||
@@ -23,9 +23,14 @@
|
||||
<span v-if="item.shortcut" class="ml-auto border border-surface rounded bg-emphasis text-muted-color text-xs p-1">{{ item.shortcut }}</span>
|
||||
<i v-if="hasSubmenu" :class="['pi pi-angle-down', { 'pi-angle-down ml-2': root, 'pi-angle-right ml-auto': !root }]"></i>
|
||||
</router-link>
|
||||
|
||||
</template>
|
||||
|
||||
<template #end>
|
||||
<div class="flex items-center gap-2">
|
||||
{{ user.firstName }}
|
||||
<Button @click="drawerStore.visible = true" label="Create"/>
|
||||
|
||||
<!-- <InputText placeholder="Search" type="text" class="w-32 sm:w-auto" />-->
|
||||
<!-- <Avatar image="https://primefaces.org/cdn/primevue/images/avatar/amyelsner.png" shape="circle" />-->
|
||||
</div>
|
||||
@@ -36,9 +41,23 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import {computed, ref} from "vue";
|
||||
import Badge from "primevue/badge";
|
||||
import Button from "primevue/button"
|
||||
import Menubar from "primevue/menubar";
|
||||
import {useUserStore} from "@/stores/userStore";
|
||||
import {useDrawerStore} from "@/stores/drawerStore.ts";
|
||||
|
||||
|
||||
const userStore = useUserStore()
|
||||
const user = computed(() => userStore.user);
|
||||
const loadingUser = computed(() => userStore.loadingUser);
|
||||
|
||||
const drawerStore = useDrawerStore()
|
||||
const visible = computed(() => drawerStore.visible);
|
||||
|
||||
|
||||
|
||||
|
||||
const items = ref([
|
||||
{
|
||||
|
||||
35
src/components/PopUp.vue
Normal file
35
src/components/PopUp.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<!-- Фон затемнения, отображается только при открытом popup -->
|
||||
<div v-if="isOpen" class="fixed inset-0 bg-black bg-opacity-50 z-40" @click="closePopup"></div>
|
||||
|
||||
<!-- Контейнер popup с анимацией -->
|
||||
<transition name="slide-up">
|
||||
<div v-if="isOpen" class="fixed bottom-0 left-0 right-0 h-[90vh] bg-white rounded-t-2xl shadow-lg z-50 flex flex-col">
|
||||
<header class="flex justify-between items-center p-4 border-b border-gray-200">
|
||||
<h2 class="text-lg font-semibold">{{ props.header }}</h2>
|
||||
<button class="text-blue-600 text-lg" @click="closePopup">Закрыть</button>
|
||||
</header>
|
||||
<div class="flex-grow overflow-y-auto p-4">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
header: String
|
||||
})
|
||||
|
||||
const isOpen = ref(true); // Управление открытием popup (можно связать с пропсом)
|
||||
const emits = defineEmits(['close-popup']);
|
||||
function closePopup() {
|
||||
emits('close-popup');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
25
src/components/StatusView.vue
Normal file
25
src/components/StatusView.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
const props = defineProps({
|
||||
isError: Boolean,
|
||||
message: String,
|
||||
show: Boolean,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="show" class="absolute top-0 left-0 w-full h-full flex items-center justify-center z-50">
|
||||
<div
|
||||
class=" px-10 py-5 rounded-lg border border-gray-200 flex flex-col items-center gap-4"
|
||||
:class="isError ? 'bg-red-100' : 'bg-green-100'"
|
||||
aria-label="Custom ProgressSpinner">
|
||||
<i class="pi pi-check " :class="isError ? 'text-red-500' : 'text-green-500'" style="font-size: 2rem;"/>
|
||||
<p class="text-green-700" :class="isError ? 'text-red-500' : 'text-green-500'">{{ message }}</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -2,14 +2,8 @@
|
||||
<div
|
||||
class=" items-center toolbar-example justify-between bg-white outline rounded-xl outline-gray-300 shadow-lg h-fit fixed"
|
||||
style="width: 90%; left:5%; bottom: 1.5rem;">
|
||||
<TransactionEditDrawer v-if="drawerOpened" :visible="drawerOpened"
|
||||
|
||||
|
||||
:transaction-type="transactionType"
|
||||
:category-type="categoryType"
|
||||
|
||||
@close-drawer="closeDrawer()"
|
||||
/>
|
||||
<!-- <TransactionForm v-if="drawerOpened" :visible="drawerOpened" :transaction-type="'INSTANT'"-->
|
||||
<!-- :category-type="'EXPENSE'" @close-drawer="closeDrawer"/>-->
|
||||
<div class="flex flex-row rounded-full px-2 justify-between overflow-x">
|
||||
<div class="flex flex-col gap-2 p-2">
|
||||
<router-link to="/budgets" class="items-center flex flex-col gap-2">
|
||||
@@ -66,10 +60,10 @@
|
||||
<button @click="openDrawer('INSTANT')" class="hover:bg-gray-100 p-2 rounded-lg">
|
||||
<p>Создать текущую</p>
|
||||
</button>
|
||||
<button @click="openDrawer('PLANNED', 'INCOME')" class="hover:bg-gray-100 p-2 rounded-lg">
|
||||
<button @click="openDrawer('PLANNED', 'EXPENSE')" class="hover:bg-gray-100 p-2 rounded-lg">
|
||||
<p class="text-left"> Создать плановый расход</p>
|
||||
</button>
|
||||
<button @click=" openDrawer('PLANNED', 'EXPENSE')" class="hover:bg-gray-100 p-2 rounded-lg">
|
||||
<button @click=" openDrawer('PLANNED', 'INCOME')" class="hover:bg-gray-100 p-2 rounded-lg">
|
||||
<p class="text-left">Создать плановое поступление</p>
|
||||
</button>
|
||||
<router-link to="/settings" class="items-center flex flex-col gap-2 p-2">
|
||||
@@ -89,32 +83,38 @@
|
||||
<script setup lang="ts">
|
||||
import {onMounted, ref} from 'vue';
|
||||
import Button from 'primevue/button';
|
||||
import TransactionEditDrawer from "@/components/budgets/TransactionEditDrawer.vue";
|
||||
import {TransactionType} from "@/models/Transaction";
|
||||
import {CategoryType} from "@/models/Category";
|
||||
import {useRoute} from "vue-router";
|
||||
import {useDrawerStore} from "@/stores/drawerStore";
|
||||
|
||||
const showSubmenu = ref(false);
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const transactionType = ref<TransactionType>()
|
||||
const categoryType = ref<CategoryType>()
|
||||
const drawerOpened = ref(false);
|
||||
|
||||
const drawerStore = useDrawerStore()
|
||||
|
||||
|
||||
const refreshPage = () => {
|
||||
window.location.reload(true)
|
||||
}
|
||||
const openDrawer = (selectedTransactionType = null, selectedCategoryType = null) => {
|
||||
if (selectedTransactionType && selectedCategoryType) {
|
||||
transactionType.value = selectedTransactionType;
|
||||
categoryType.value = selectedCategoryType;
|
||||
// transactionType.value = selectedTransactionType;
|
||||
// categoryType.value = selectedCategoryType;
|
||||
drawerStore.setTransactionType(selectedTransactionType)
|
||||
drawerStore.setCategoryType(selectedTransactionType)
|
||||
} else if (selectedTransactionType) {
|
||||
transactionType.value = selectedTransactionType;
|
||||
categoryType.value = 'EXPENSE'
|
||||
// transactionType.value = selectedTransactionType;
|
||||
// categoryType.value = 'EXPENSE'
|
||||
drawerStore.setTransactionType(selectedTransactionType)
|
||||
drawerStore.setCategoryType('EXPENSE')
|
||||
}
|
||||
|
||||
drawerOpened.value = true;
|
||||
console.log(selectedTransactionType)
|
||||
console.log(selectedCategoryType)
|
||||
drawerStore.setVisible( true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
62
src/components/TransactionForm.vue
Normal file
62
src/components/TransactionForm.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="card flex justify-center h-fit">
|
||||
<DrawerForm v-if="isDesktop" :visible="visible" :isEditing="isEditing" @close-drawer="closeDrawer">
|
||||
<template #default>
|
||||
<TransactionFormContent :transaction="props.transaction" :transaction-type="transactionType" :category-type="categoryType" @close-drawer="closeDrawer" @create-transaction="transactionUpdated"
|
||||
@delete-transaction="transactionUpdated" @transaction-updated="transactionUpdated" />
|
||||
</template>
|
||||
</DrawerForm>
|
||||
|
||||
<PopUp v-else :header="'Создать транзакцию'" @close-popup="closeDrawer">
|
||||
<template #default>
|
||||
<TransactionFormContent :transaction="props.transaction" :transaction-type="transactionType" :category-type="categoryType" @close-drawer="closeDrawer" @create-transaction="transactionUpdated"
|
||||
@delete-transaction="transactionUpdated" @transaction-updated="transactionUpdated" />
|
||||
</template>
|
||||
</PopUp>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import DrawerForm from "@/components/DrawerForm.vue";
|
||||
import PopUp from "@/components/PopUp.vue";
|
||||
import TransactionFormContent from "@/components/TransactionFormContent.vue";
|
||||
import {Transaction} from "@/models/Transaction";
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
transaction: {
|
||||
type: Object as Transaction,
|
||||
required: false
|
||||
},
|
||||
transactionType: String,
|
||||
categoryType: String,
|
||||
})
|
||||
|
||||
const isDesktop = ref(window.innerWidth >= 1024);
|
||||
const visible = ref(true); // Устанавливаем true или false для показа/скрытия
|
||||
const isEditing = ref(false); // Определяем, редактирование или создание транзакции
|
||||
|
||||
const emit = defineEmits([ 'close-drawer', 'transaction-updated']);
|
||||
|
||||
|
||||
// Обновляем `isDesktop` при изменении размера экрана
|
||||
function updateIsDesktop() {
|
||||
isDesktop.value = window.innerWidth >= 1024;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', updateIsDesktop);
|
||||
});
|
||||
|
||||
const closeDrawer = () => {
|
||||
console.log("close drawer");
|
||||
visible.value = false;
|
||||
emit('close-drawer');
|
||||
};
|
||||
|
||||
const transactionUpdated = () => {
|
||||
emit("transaction-updated");
|
||||
}
|
||||
|
||||
</script>
|
||||
438
src/components/TransactionFormContent.vue
Normal file
438
src/components/TransactionFormContent.vue
Normal file
@@ -0,0 +1,438 @@
|
||||
<script setup lang="ts">
|
||||
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 platform from 'platform';
|
||||
import {
|
||||
createTransactionRequest,
|
||||
getTransactionTypes,
|
||||
updateTransactionRequest,
|
||||
deleteTransactionRequest, getTransactions
|
||||
} from "@/services/transactionService";
|
||||
import {getCategories, getCategoryTypes} from "@/services/categoryService";
|
||||
import {useToast} from "primevue/usetoast";
|
||||
import LoadingView from "@/components/LoadingView.vue";
|
||||
import BudgetTransactionView from "@/components/budgets/BudgetTransactionView.vue";
|
||||
import {useUserStore} from "@/stores/userStore";
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
transaction: {
|
||||
type: Object as () => Transaction,
|
||||
required: false
|
||||
},
|
||||
transactionType: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
categoryType: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
transactions: {
|
||||
type: Array as () => Array<Transaction>,
|
||||
required: false
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['create-transaction', 'update-transaction', 'delete-transaction', 'close-drawer', 'transaction-updated']);
|
||||
const toast = useToast();
|
||||
const categoryTypeChanged = () => {
|
||||
|
||||
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(true);
|
||||
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 userStore = useUserStore();
|
||||
const user = computed( () => userStore.user)
|
||||
|
||||
const isReady = computed(() => !loading.value && loadingUser.value)
|
||||
|
||||
// Получение категорий и типов транзакций
|
||||
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 checkForm = () => {
|
||||
const errorMessages = {
|
||||
transactionType: 'Тип транзакции должен быть выбран',
|
||||
category: 'Категория должна быть выбрана',
|
||||
date: 'Дата должна быть выбрана',
|
||||
comment: 'Комментарий должен быть введен',
|
||||
amount: 'Сумма не может быть пустой или 0'
|
||||
};
|
||||
|
||||
if (!editedTransaction.value.transactionType) return showError(errorMessages.transactionType);
|
||||
if (!editedTransaction.value.category) return showError(errorMessages.category);
|
||||
if (!editedTransaction.value.date) return showError(errorMessages.date);
|
||||
if (!editedTransaction.value.comment) return showError(errorMessages.comment);
|
||||
if (!editedTransaction.value.amount || editedTransaction.value.amount === 0) return showError(errorMessages.amount);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Инициализация данных
|
||||
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;
|
||||
console.log('here')
|
||||
selectedTransactionType.value = editedTransaction.value.transactionType;
|
||||
}
|
||||
|
||||
};
|
||||
const result = ref(false)
|
||||
const isError = ref(false)
|
||||
const resultText = ref('')
|
||||
|
||||
const computeResult = (resultState, error) => {
|
||||
|
||||
if (!resultState && error) {
|
||||
result.value = true;
|
||||
isError.value = true
|
||||
resultText.value = `Ошибка: ${error.message}`
|
||||
} else {
|
||||
|
||||
result.value = true;
|
||||
isError.value = false
|
||||
resultText.value = 'Успех!'
|
||||
}
|
||||
setTimeout(() => {
|
||||
result.value = false
|
||||
resultText.value = ''
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const showError = (message) => {
|
||||
result.value = true;
|
||||
isError.value = true;
|
||||
resultText.value = message;
|
||||
return false;
|
||||
};
|
||||
|
||||
// Создание транзакции
|
||||
const createTransaction = async () => {
|
||||
if (checkForm()) {
|
||||
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);
|
||||
computeResult(true)
|
||||
resetForm();
|
||||
} catch (error) {
|
||||
computeResult(false, error)
|
||||
console.error('Error creating transaction:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
result.value = false
|
||||
resultText.value = ''
|
||||
}, 1000)
|
||||
};
|
||||
|
||||
// Обновление транзакции
|
||||
const updateTransaction = async () => {
|
||||
if (checkForm()) {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await updateTransactionRequest(editedTransaction.value);
|
||||
response.data;
|
||||
// toast.add({severity: 'success', summary: 'Transaction updated!', detail: 'Транзакция обновлена!', life: 3000});
|
||||
emit('update-transaction', editedTransaction.value);
|
||||
emit('transaction-updated');
|
||||
computeResult(true)
|
||||
} catch (error) {
|
||||
computeResult(false, error)
|
||||
console.error('Error updating transaction:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
result.value = false
|
||||
resultText.value = ''
|
||||
|
||||
}, 1000)
|
||||
};
|
||||
|
||||
// Удаление транзакции
|
||||
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()
|
||||
computeResult(true)
|
||||
} catch (error) {
|
||||
computeResult(false, error)
|
||||
toast.add({severity: 'warn', summary: 'Error!', detail: 'Транзакция обновлена!', life: 3000});
|
||||
|
||||
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(() => {
|
||||
|
||||
if (editedTransaction.value.transactionType.code != 'PLANNED' && editedTransaction.value.date > new Date()) {
|
||||
|
||||
return 'При мгновенных тратах дата должна быть меньше текущей!'
|
||||
} else if (editedTransaction.value.transactionType.code == 'PLANNED' && editedTransaction.value.date < new Date()) {
|
||||
|
||||
return 'При плановых тратах дата должна быть больше текущей!'
|
||||
} else {
|
||||
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
// Закрытие окна
|
||||
const closeDrawer = () => emit('close-drawer');
|
||||
const keyboardOpen = ref(false);
|
||||
const isMobile = ref(false);
|
||||
const userAgent = ref(null);
|
||||
const transactions = ref<Transaction[]>(props.transactions);
|
||||
// Мониторинг при монтировании
|
||||
onMounted(async () => {
|
||||
|
||||
loading.value = true;
|
||||
|
||||
await fetchCategoriesAndTypes();
|
||||
|
||||
prepareData();
|
||||
|
||||
if ( !isEditing.value) {
|
||||
await getTransactions('INSTANT', 'EXPENSE',null, user.value.id ).then(transactionsResponse => transactions.value = transactionsResponse.data);
|
||||
transactions.value = transactions.value.slice(0,3)
|
||||
console.log(transactions.value.slice(0,3))
|
||||
}
|
||||
loading.value = false;
|
||||
const deviceInfo = platform;
|
||||
isMobile.value = deviceInfo.os.family === 'iOS' || deviceInfo.os.family === 'Android';
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div class="card flex justify-center h-fit">
|
||||
|
||||
|
||||
|
||||
|
||||
<div v-if="result" class="absolute top-0 left-0 w-full h-full flex items-center justify-center z-50">
|
||||
<div
|
||||
class=" px-10 py-5 rounded-lg border border-gray-200 flex flex-col items-center gap-4"
|
||||
:class="isError ? 'bg-red-100' : 'bg-green-100'"
|
||||
aria-label="Custom ProgressSpinner">
|
||||
<i class="pi pi-check " :class="isError ? 'text-red-500' : 'text-green-500'" style="font-size: 2rem;"/>
|
||||
<p class="text-green-700" :class="isError ? 'text-red-500' : 'text-green-500'">{{ resultText }}</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="absolute w-full h-screen">
|
||||
<!-- Полупрозрачный белый фон -->
|
||||
<!-- <div class="absolute top-0 left-0 w-full h-full bg-white opacity-50 z-0"></div>-->
|
||||
|
||||
<!-- Спиннер поверх -->
|
||||
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<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-40"
|
||||
@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 line-clamp-1">{{ 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-y-auto 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"
|
||||
@focus="keyboardOpen=true"
|
||||
@blur="keyboardOpen=false"
|
||||
|
||||
/>
|
||||
<label for="amount" class="">Сумма</label>
|
||||
</FloatLabel>
|
||||
|
||||
<!-- Comment Input -->
|
||||
|
||||
<FloatLabel variant="on" class="w-full">
|
||||
<label for="comment">Комментарий</label>
|
||||
<InputText class="w-full"
|
||||
:invalid="!editedTransaction.comment"
|
||||
id="comment"
|
||||
v-model="editedTransaction.comment"
|
||||
@focus="keyboardOpen=true"
|
||||
@blur="keyboardOpen=false"
|
||||
/>
|
||||
</FloatLabel>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Date Picker -->
|
||||
<div class="field col-12 gap-0">
|
||||
<FloatLabel variant="on">
|
||||
<label for="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>
|
||||
<div>
|
||||
<BudgetTransactionView v-if="!isEditing && transactions" v-for="transaction in transactions" :is-list="true"
|
||||
:transaction="transaction"/>
|
||||
</div>
|
||||
|
||||
<div class="fixed col-12 flex justify-content-end gap-4 bottom-8">
|
||||
|
||||
<Button label="Сохранить" icon="pi pi-check" class="p-button-success"
|
||||
@click="isEditing ? updateTransaction() : createTransaction()"/>
|
||||
<Button label="Отмена" icon="pi pi-times" class="p-button-secondary " @click="closeDrawer"/>
|
||||
<Button v-if="isEditing" label="Удалить" icon="pi pi-times" class="p-button-success" severity="danger"
|
||||
@click="deleteTransaction"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
99
src/components/budgets/BudgetCreationView.vue
Normal file
99
src/components/budgets/BudgetCreationView.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<script setup lang="ts">
|
||||
import Dialog from "primevue/dialog";
|
||||
import Checkbox from "primevue/checkbox";
|
||||
import Button from "primevue/button";
|
||||
import InputText from "primevue/inputtext";
|
||||
import FloatLabel from "primevue/floatlabel";
|
||||
import DatePicker from "primevue/datepicker";
|
||||
import {onMounted, ref} from "vue";
|
||||
import {getMonthName} from "@/utils/utils";
|
||||
import {Budget} from "@/models/Budget";
|
||||
import {createBudget} from "@/services/budgetsService";
|
||||
|
||||
const props = defineProps({
|
||||
opened: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const emits = defineEmits(['close-modal'])
|
||||
const createRecurrentPayments = ref<Boolean>(true)
|
||||
|
||||
const name = ref('')
|
||||
const dateFrom = ref(new Date())
|
||||
const dateTo = ref(new Date())
|
||||
|
||||
const budget = ref(new Budget())
|
||||
|
||||
const create = async () => {
|
||||
console.log(budget.value)
|
||||
try {
|
||||
await createBudget(budget.value, createRecurrentPayments.value)
|
||||
emits("close-modal");
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
emits("close-modal");
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
budget.value.name = ''
|
||||
budget.value.dateTo = new Date();
|
||||
budget.value.dateFrom = new Date();
|
||||
budget.value.dateFrom.setDate(10)
|
||||
if (budget.value.dateFrom.getMonth() == 11) {
|
||||
budget.value.dateFrom.setMonth(0)
|
||||
} else {
|
||||
budget.value.dateFrom.setMonth(dateFrom.value.getMonth() + 1)
|
||||
}
|
||||
budget.value.dateTo.setDate(9)
|
||||
if (budget.value.dateTo.getMonth() == 10) {
|
||||
budget.value.dateTo.setMonth(0)
|
||||
budget.value.dateTo.setYear(dateTo.value.getFullYear() + 1)
|
||||
} else {
|
||||
budget.value.dateTo.setMonth(budget.value.dateTo.getMonth() + 2)
|
||||
}
|
||||
budget.value.name = getMonthName(budget.value.dateFrom.getMonth()) + ' ' + budget.value.dateFrom.getFullYear();
|
||||
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog :visible="opened" modal header="Создать новый бюджет" :style="{ width: '25rem' }">
|
||||
<div class="flex flex-col gap-4 mt-1">
|
||||
<FloatLabel variant="on" class="w-full">
|
||||
<label for="name">Название</label>
|
||||
<InputText v-model="budget.name" id="name" class="w-full"/>
|
||||
</FloatLabel>
|
||||
<div class="flex flex-row gap-4">
|
||||
<FloatLabel variant="on">
|
||||
<label for="dateFrom">Дата начала</label>
|
||||
<DatePicker v-model="budget.dateFrom" id="dateFrom" dateFormat="dd.mm.yy"/>
|
||||
</FloatLabel>
|
||||
<FloatLabel variant="on">
|
||||
<label for="dateTo">Дата завершения</label>
|
||||
<DatePicker v-model="budget.dateTo" id="dateTo" dateFormat="dd.mm.yy"/>
|
||||
</FloatLabel>
|
||||
</div>
|
||||
<div class="flex flex-row items-center min-w-fit gap-4">
|
||||
<Checkbox v-model="createRecurrentPayments" binary/>
|
||||
Создать ежемесячные платежи?
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 justify-end items-center">
|
||||
<Button label="Создать" severity="success" icon="pi pi-save" @click="create"/>
|
||||
<Button label="Отмена" severity="secondary" icon="pi pi-times-circle"/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,13 +1,18 @@
|
||||
<template>
|
||||
<LoadingView v-if="loading"/>
|
||||
<div v-else class="px-4 bg-gray-100 h-full ">
|
||||
<div v-else class="px-4 bg-gray-100 h-full flex flex-col gap-4 ">
|
||||
<!-- Заголовок -->
|
||||
<h2 class="text-4xl mb-6 font-bold">Monthly Budgets</h2>
|
||||
|
||||
<div class="flex flex-row gap-4 items-center">
|
||||
<h2 class="text-4xl font-bold">Бюджеты</h2>
|
||||
<Button label="+ Создать" @click="creationOpened=true" size="small"/>
|
||||
<BudgetCreationView :opened="creationOpened" @close-modal="creationOpened = false; creationSuccessShow() "/>
|
||||
<StatusView :show="creationSuccessModal" :is-error="false" :message="'Бюджет создан!'"/>
|
||||
</div>
|
||||
<!-- Плитка с бюджетами -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Будущие и текущие бюджеты -->
|
||||
<div v-for="budget in budgetInfos" :key="budget.id" class="p-4 shadow-lg rounded-lg bg-white" :class="budget.dateTo < new Date() ? 'bg-gray-100 opacity-60' : ''">
|
||||
<div v-for="budget in budgetInfos" :key="budget.id" class="p-4 shadow-lg rounded-lg bg-white"
|
||||
:class="budget.dateTo < new Date() ? 'bg-gray-100 opacity-60' : ''">
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="text-xl font-bold mb-2">{{ budget.name }}</div>
|
||||
<router-link :to="'/budgets/'+budget.id">
|
||||
@@ -18,14 +23,14 @@
|
||||
{{ formatDate(budget.dateFrom) }} - {{ formatDate(budget.dateTo) }}
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<!-- <div class="text-sm">Total Income: <span class="font-bold">{{ formatAmount(budgettotalIncomes) }} ₽</span></div>-->
|
||||
<!-- <div class="text-sm">Total Expenses: <span class="font-bold">{{ formatAmount(budget.totalExpenses) }} ₽</span></div>-->
|
||||
<!-- <div class="text-sm">Planned Expenses: <span class="font-bold">{{ formatAmount(budget.totalExpenses) }} ₽</span></div>-->
|
||||
<!-- <div class="text-sm">Total Income: <span class="font-bold">{{ formatAmount(budgettotalIncomes) }} ₽</span></div>-->
|
||||
<!-- <div class="text-sm">Total Expenses: <span class="font-bold">{{ formatAmount(budget.totalExpenses) }} ₽</span></div>-->
|
||||
<!-- <div class="text-sm">Planned Expenses: <span class="font-bold">{{ formatAmount(budget.totalExpenses) }} ₽</span></div>-->
|
||||
<div class="text-sm flex items-center">
|
||||
Unplanned Expenses:
|
||||
<!-- <span class="ml-2 font-bold">{{ formatAmount(budget.totalIncomes - budget.totalExpenses) }} ₽</span>-->
|
||||
<!-- <span class="ml-2 font-bold">{{ formatAmount(budget.totalIncomes - budget.totalExpenses) }} ₽</span>-->
|
||||
<!-- Прогресс бар -->
|
||||
<!-- <ProgressBar :value="budget.unplannedProgress" class="ml-4 w-full"/>-->
|
||||
<!-- <ProgressBar :value="budget.unplannedProgress" class="ml-4 w-full"/>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -57,36 +62,25 @@ import {onMounted, ref} from 'vue';
|
||||
import ProgressBar from 'primevue/progressbar';
|
||||
import {BudgetInfo} from "@/models/Budget";
|
||||
import {getBudgetInfos} from "@/services/budgetsService";
|
||||
import { formatDate} from "@/utils/utils";
|
||||
import {formatDate} from "@/utils/utils";
|
||||
import LoadingView from "@/components/LoadingView.vue";
|
||||
import Button from "primevue/button";
|
||||
import BudgetCreationView from "@/components/budgets/BudgetCreationView.vue";
|
||||
import StatusView from "@/components/StatusView.vue";
|
||||
|
||||
|
||||
const loading = ref(false)
|
||||
const budgetInfos = ref<BudgetInfo[]>([])
|
||||
|
||||
const upcomingBudgets = ref([
|
||||
{
|
||||
id: 1,
|
||||
month: 'October 2024',
|
||||
startDate: '2024-10-01',
|
||||
endDate: '2024-10-31',
|
||||
totalIncome: '500,000 RUB',
|
||||
totalExpenses: '350,000 RUB',
|
||||
plannedExpenses: '300,000 RUB',
|
||||
remainingForUnplanned: '50,000 RUB',
|
||||
unplannedProgress: 60, // Прогресс в процентах
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
month: 'November 2024',
|
||||
startDate: '2024-11-01',
|
||||
endDate: '2024-11-30',
|
||||
totalIncome: '550,000 RUB',
|
||||
totalExpenses: '320,000 RUB',
|
||||
plannedExpenses: '250,000 RUB',
|
||||
remainingForUnplanned: '70,000 RUB',
|
||||
unplannedProgress: 50,
|
||||
},
|
||||
]);
|
||||
const creationOpened = ref(false)
|
||||
const creationSuccessModal = ref(false)
|
||||
const creationSuccessShow = async () => {
|
||||
budgetInfos.value = await getBudgetInfos()
|
||||
creationSuccessModal.value = true
|
||||
setTimeout(() => {
|
||||
creationSuccessModal.value = false
|
||||
}
|
||||
, 1000)
|
||||
}
|
||||
const pastBudgets = ref([
|
||||
{
|
||||
id: 3,
|
||||
@@ -112,6 +106,8 @@ const pastBudgets = ref([
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
budgetInfos.value = await getBudgetInfos()
|
||||
|
||||
@@ -4,11 +4,11 @@ import Button from "primevue/button";
|
||||
import Checkbox from "primevue/checkbox";
|
||||
import {computed, onMounted, PropType, ref} from "vue";
|
||||
import {Transaction} from "@/models/Transaction";
|
||||
import TransactionEditDrawer from "@/components/budgets/TransactionEditDrawer.vue";
|
||||
import {Category, CategoryType} from "@/models/Category";
|
||||
import {getCategories, getCategoryTypes} from "@/services/categoryService";
|
||||
import {setTransactionDoneRequest} from "@/services/transactionService";
|
||||
import {formatAmount, formatDate} from "@/utils/utils";
|
||||
import TransactionForm from "@/components/TransactionForm.vue";
|
||||
|
||||
|
||||
const props = defineProps(
|
||||
@@ -135,14 +135,12 @@ onMounted(async () => {
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<TransactionEditDrawer v-if="drawerOpened" :visible="drawerOpened" :expenseCategories="expenseCategories"
|
||||
:incomeCategories="incomeCategories" :transaction="transaction"
|
||||
:category-types="categoryTypes"
|
||||
@transaction-updated="transactionUpdate"
|
||||
@delete-transaction="transactionUpdate"
|
||||
@create-transaction="transactionUpdate"
|
||||
@close-drawer="closeDrawer()"
|
||||
/>
|
||||
|
||||
|
||||
<TransactionForm v-if="drawerOpened" :visible="drawerOpened" :transaction="transaction"
|
||||
@close-drawer="closeDrawer" @transaction-updated="transactionUpdate"
|
||||
@delete-transaction="transactionUpdate"
|
||||
@create-transaction="transactionUpdate"/>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
<div class="flex flex-col ">
|
||||
<!-- {{ budget }}-->
|
||||
<h2 class="text-4xl font-bold">Бюджет {{ budget.name }} </h2>
|
||||
<div class="flex flex-row gap-2 text-xl">{{ formatDate(budget.dateFrom) }} -
|
||||
{{ formatDate(budget.dateTo) }}
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 text-xl">{{ formatDate(budget.dateFrom) }} -
|
||||
{{ formatDate(budget.dateTo) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<!-- Аналитика и плановые доходы/расходы -->
|
||||
@@ -58,8 +58,9 @@
|
||||
</div>
|
||||
<div class="flex flex-col items-center ">
|
||||
<h4 class="text-lg font-bold ">Расходы</h4>
|
||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center" :class="totalExpenses > totalIncomes ? ' text-red-700' : ''">
|
||||
-{{ formatAmount(totalExpenses) }} ({{formatAmount(totalExpenses- totalIncomes)}})
|
||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center"
|
||||
:class="totalExpenses > totalIncomes ? ' text-red-700' : ''">
|
||||
-{{ formatAmount(totalExpenses) }} ({{ formatAmount(totalExpenses - totalIncomes) }})
|
||||
₽
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,7 +109,8 @@
|
||||
</div>
|
||||
<div class="flex flex-col items-center ">
|
||||
<span class="text-sm lg:text-base">Сбережения</span>
|
||||
<div class="font-light bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center" :class="savingRatio < 30 ? '!font-bold text-red-700' : ''">
|
||||
<div class="font-light bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center"
|
||||
:class="savingRatio < 30 ? '!font-bold text-red-700' : ''">
|
||||
{{ savingRatio.toFixed(0) }} %
|
||||
</div>
|
||||
</div>
|
||||
@@ -261,16 +263,21 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<TransactionEditDrawer v-if="drawerOpened" :visible="drawerOpened"
|
||||
:transaction-type="transactionType"
|
||||
:category-type="categoryType"
|
||||
:transactions="transactions.slice(0,3)"
|
||||
@transaction-updated="updateTransactions"
|
||||
@delete-transaction="updateTransactions"
|
||||
@create-transaction="updateTransactions"
|
||||
@close-drawer="closeDrawer"
|
||||
<!-- <TransactionEditDrawer v-if="drawerOpened" :visible="drawerOpened"-->
|
||||
<!-- :transaction-type="transactionType"-->
|
||||
<!-- :category-type="categoryType"-->
|
||||
<!-- :transactions="transactions.slice(0,3)"-->
|
||||
<!-- @transaction-updated="updateTransactions"-->
|
||||
<!-- @delete-transaction="updateTransactions"-->
|
||||
<!-- @create-transaction="updateTransactions"-->
|
||||
<!-- @close-drawer="closeDrawer"-->
|
||||
|
||||
/>
|
||||
<!-- />-->
|
||||
|
||||
<TransactionForm v-if="drawerOpened" :visible="drawerOpened" :transaction-type="transactionType"
|
||||
:category-type="categoryType" @close-drawer="closeDrawer" @transaction-updated="updateTransactions"
|
||||
@delete-transaction="updateTransactions"
|
||||
@create-transaction="updateTransactions"/>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
@@ -299,7 +306,7 @@ import LoadingView from "@/components/LoadingView.vue";
|
||||
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
||||
import {Chart as ChartJS} from 'chart.js/auto';
|
||||
import SelectButton from "primevue/selectbutton";
|
||||
import TransactionEditDrawer from "@/components/budgets/TransactionEditDrawer.vue";
|
||||
import TransactionForm from "@/components/TransactionForm.vue";
|
||||
|
||||
// Зарегистрируем плагин
|
||||
ChartJS.register(ChartDataLabels);
|
||||
@@ -529,9 +536,9 @@ const incomesByPeriod = computed(() => {
|
||||
let incomesUntil25 = 0
|
||||
let incomesFrom25 = 0
|
||||
plannedIncomes.value.forEach((i) => {
|
||||
console.log(i.date)
|
||||
|
||||
if (i.date >= budget.value?.dateFrom && i.date <= twentyFour.value) {
|
||||
console.log(i.date)
|
||||
|
||||
incomesUntil25 += i.amount
|
||||
} else {
|
||||
incomesFrom25 += i.amount
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import Drawer from "primevue/drawer";
|
||||
import InputText from "primevue/inputtext";
|
||||
import DatePicker from "primevue/datepicker";
|
||||
import FloatLabel from "primevue/floatlabel";
|
||||
@@ -21,6 +20,8 @@ import {getCategories, getCategoryTypes} from "@/services/categoryService";
|
||||
import {useToast} from "primevue/usetoast";
|
||||
import LoadingView from "@/components/LoadingView.vue";
|
||||
import BudgetTransactionView from "@/components/budgets/BudgetTransactionView.vue";
|
||||
import {useUserStore} from "@/stores/userStore";
|
||||
import DrawerForm from "@/components/DrawerForm.vue";
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -73,6 +74,11 @@ const incomeCategories = ref<Category[]>([]);
|
||||
const categoryTypes = ref<CategoryType[]>([]);
|
||||
const transactionTypes = ref<TransactionType[]>([]);
|
||||
|
||||
const userStore = useUserStore();
|
||||
const user = computed( () => userStore.user)
|
||||
|
||||
const isReady = computed(() => !loading.value && loadingUser.value)
|
||||
|
||||
// Получение категорий и типов транзакций
|
||||
const fetchCategoriesAndTypes = async () => {
|
||||
try {
|
||||
@@ -260,9 +266,9 @@ onMounted(async () => {
|
||||
await fetchCategoriesAndTypes();
|
||||
|
||||
prepareData();
|
||||
if (!transactions.value && !isEditing.value) {
|
||||
console.log()
|
||||
await getTransactions('INSTANT', 'EXPENSE' ).then(transactionsResponse => transactions.value = transactionsResponse.data);
|
||||
|
||||
if ( !isEditing.value) {
|
||||
await getTransactions('INSTANT', 'EXPENSE',null, user.value.id ).then(transactionsResponse => transactions.value = transactionsResponse.data);
|
||||
transactions.value = transactions.value.slice(0,3)
|
||||
console.log(transactions.value.slice(0,3))
|
||||
}
|
||||
@@ -278,9 +284,8 @@ onMounted(async () => {
|
||||
<div class="card flex justify-center h-dvh">
|
||||
|
||||
|
||||
<Drawer :visible="visible" :header="isEditing ? 'Изменить транзакцию' : 'Создать транзакцию'" :showCloseIcon="false"
|
||||
position="right" @hide="closeDrawer"
|
||||
class="!w-128 ">
|
||||
<DrawerForm>
|
||||
|
||||
<div v-if="result" class="absolute top-0 left-0 w-full h-full flex items-center justify-center z-50">
|
||||
<div
|
||||
class=" px-10 py-5 rounded-lg border border-gray-200 flex flex-col items-center gap-4"
|
||||
@@ -301,11 +306,9 @@ onMounted(async () => {
|
||||
|
||||
<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"
|
||||
@@ -427,7 +430,7 @@ onMounted(async () => {
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Drawer>
|
||||
</DrawerForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@ import InputIcon from "primevue/inputicon";
|
||||
import InputText from "primevue/inputtext";
|
||||
import {getTransactions} from "@/services/transactionService";
|
||||
import {Transaction} from "@/models/Transaction";
|
||||
import {useRoute} from "vue-router";
|
||||
|
||||
|
||||
const loading = ref(false);
|
||||
const searchText = ref("");
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
|
||||
|
||||
const fetchCategories = async () => {
|
||||
@@ -63,6 +63,7 @@ onMounted(async () => {
|
||||
<div class="px-4 bg-gray-100 h-full ">
|
||||
<!-- Заголовок -->
|
||||
<!-- {{tgname}}-->
|
||||
|
||||
<h2 class="text-4xl mb-6 font-bold">Transaction list</h2>
|
||||
<div class="flex flex-col gap-2">
|
||||
<IconField>
|
||||
@@ -70,11 +71,12 @@ onMounted(async () => {
|
||||
<InputText v-model="searchText" placeholder="Search"></InputText>
|
||||
</IconField>
|
||||
|
||||
<div class="mt-4">
|
||||
<BudgetTransactionView class="mb-2" v-for="transaction in filteredTransactions" :transaction="transaction" :is-list="true"/>
|
||||
<div class=" flex flex-col gap-2">
|
||||
<BudgetTransactionView v-for="transaction in filteredTransactions" :transaction="transaction" :is-list="true"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user