234 lines
9.1 KiB
Vue
234 lines
9.1 KiB
Vue
<template>
|
||
<Dialog :visible="show" modal :header="isEditing ? 'Edit Recurrent Payment' : 'Create Recurrent Payment'"
|
||
:closable="true" class="!w-1/3">
|
||
<div v-if="loading">
|
||
Loading...
|
||
</div>
|
||
<div v-else class="p-fluid flex flex-col gap-6 w-full py-6 items-start">
|
||
<!-- Название -->
|
||
<FloatLabel class="w-full">
|
||
<label for="paymentName">Payment Name</label>
|
||
<InputText v-model="name" id="paymentName" class="!w-full"/>
|
||
</FloatLabel>
|
||
|
||
<!-- Категория -->
|
||
<div class="relative w-full justify-center justify-items-center ">
|
||
<div class="flex flex-col justify-items-center gap-2">
|
||
<SelectButton v-model="selectedCategoryType" :options="categoryTypes" optionLabel="name"
|
||
aria-labelledby="basic"
|
||
@change="categoryTypeChanged(selectedCategoryType.code)" 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 gap-4">
|
||
<div class="flex flex-row justify-between w-full px-4">
|
||
<p class="text-6xl font-bold text-gray-700 dark:text-gray-400">{{ selectedCategory.icon }}</p>
|
||
<div class="flex flex-col items-start justify-items-start justify-around w-full">
|
||
<p class="font-bold text-start">{{ selectedCategory.name }}</p>
|
||
<p class="font-light line-clamp-1 items-start text-start">{{ selectedCategory.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 selectedCategoryType.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>
|
||
|
||
<!-- Описание -->
|
||
<FloatLabel class="w-full">
|
||
<label for="description">Description</label>
|
||
<Textarea v-model="description" id="description" rows="3"/>
|
||
</FloatLabel>
|
||
|
||
<!-- Дата повторения (выпадающий список) -->
|
||
<div class="w-full relative">
|
||
<button class="border border-gray-300 rounded-lg w-full z-50"
|
||
@click="isDaySelectorOpened = !isDaySelectorOpened">
|
||
<div class="flex flex-row items-center pe-4 py-2 gap-4">
|
||
<div class="flex flex-row justify-between w-full px-4">
|
||
<p class="font-bold">Повторять каждый {{ repeatDay || 'N' }} день месяца</p>
|
||
</div>
|
||
<div>
|
||
<span :class="{'rotate-90': isDaySelectorOpened}"
|
||
class="pi pi-angle-right transition-transform duration-300 text-5xl"/>
|
||
</div>
|
||
</div>
|
||
</button>
|
||
|
||
<!-- Анимированное открытие списка дней -->
|
||
<div v-show="isDaySelectorOpened"
|
||
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': !isDaySelectorOpened, 'max-h-[500px]': isDaySelectorOpened }">
|
||
<div class="grid grid-cols-7 p-2">
|
||
<button v-for="day in days" :key="day" class=" border"
|
||
@click="selectDay(day)">
|
||
{{ day }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Сумма -->
|
||
<InputGroup class="w-full">
|
||
<InputGroupAddon>₽</InputGroupAddon>
|
||
<InputNumber v-model="amount" placeholder="Amount"/>
|
||
<InputGroupAddon>.00</InputGroupAddon>
|
||
</InputGroup>
|
||
|
||
<!-- Кнопки -->
|
||
<div class="flex justify-content-end gap-2 mt-4">
|
||
<Button label="Save" icon="pi pi-check" @click="savePayment" class="p-button-success"/>
|
||
<Button label="Cancel" icon="pi pi-times" @click="closeModal" class="p-button-secondary"/>
|
||
</div>
|
||
</div>
|
||
</Dialog>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import {ref, watch, computed, defineEmits} from 'vue';
|
||
import Button from 'primevue/button';
|
||
import InputText from 'primevue/inputtext';
|
||
import InputNumber from 'primevue/inputnumber';
|
||
import Textarea from "primevue/textarea";
|
||
|
||
import Dialog from 'primevue/dialog';
|
||
import FloatLabel from 'primevue/floatlabel';
|
||
import InputGroup from 'primevue/inputgroup';
|
||
import InputGroupAddon from "primevue/inputgroupaddon";
|
||
import SelectButton from 'primevue/selectbutton';
|
||
import {saveRecurrentPayment} from "@/services/recurrentService";
|
||
|
||
const props = defineProps({
|
||
show: Boolean, // Показать/скрыть модальное окно
|
||
expenseCategories: Array, // Внешние данные для списка категорий
|
||
incomeCategories: Array, // Внешние данные для списка категорий
|
||
categoryTypes: Array,
|
||
payment: Object | null // Для редактирования существующего платежа
|
||
})
|
||
|
||
const emits = defineEmits(["open-edit", "save-payment", "close-modal"]);
|
||
|
||
const loading = ref(false)
|
||
// Поля для формы
|
||
const name = ref('');
|
||
const selectedCategoryType = ref(props.payment ? props.payment.type : props.categoryTypes[0]);
|
||
const selectedCategory = ref(selectedCategoryType.code == 'EXPESE' ? props.expenseCategories[0] : props.incomeCategories[0]);
|
||
|
||
const categoryTypeChanged = (code) => {
|
||
|
||
selectedCategory.value = code == "EXPENSE" ? props.expenseCategories[0] : props.incomeCategories[0];
|
||
}
|
||
|
||
const description = ref('');
|
||
const repeatDay = ref<number | null>(null);
|
||
const amount = ref<number | null>(null);
|
||
|
||
// Открытие/закрытие списка категорий
|
||
const isCategorySelectorOpened = ref(false);
|
||
const isDaySelectorOpened = ref(false);
|
||
|
||
// Список дней (1–31)
|
||
const days = Array.from({length: 31}, (_, i) => i + 1);
|
||
|
||
// Выбор дня
|
||
const selectDay = (day: number) => {
|
||
repeatDay.value = day;
|
||
isDaySelectorOpened.value = false;
|
||
};
|
||
|
||
// Выбор категории
|
||
const selectCategory = (category) => {
|
||
isCategorySelectorOpened.value = false;
|
||
selectedCategory.value = category;
|
||
};
|
||
|
||
// Определение, редактируем ли мы существующий платеж
|
||
const isEditing = computed(() => !!props.payment);
|
||
|
||
// Слушаем изменения, если редактируем существующий платеж
|
||
watch(() => props.payment, (newPayment) => {
|
||
if (newPayment) {
|
||
name.value = newPayment.name;
|
||
selectedCategory.value = newPayment.category;
|
||
description.value = newPayment.description;
|
||
repeatDay.value = newPayment.repeatDay;
|
||
amount.value = newPayment.amount;
|
||
} else {
|
||
resetForm();
|
||
}
|
||
});
|
||
|
||
// Функция для сохранения платежа
|
||
const savePayment = async () => {
|
||
loading.value = true;
|
||
const paymentData = {
|
||
name: name.value,
|
||
category: selectedCategory.value,
|
||
description: description.value,
|
||
atDay: repeatDay.value,
|
||
amount: amount.value
|
||
};
|
||
|
||
if (isEditing.value && props.payment) {
|
||
paymentData.id = props.payment.id; // Если редактируем, сохраняем ID
|
||
}
|
||
try {
|
||
await saveRecurrentPayment(paymentData)
|
||
loading.value = false
|
||
resetForm();
|
||
} catch (error) {
|
||
console.error('Error saving payment:', error);
|
||
}
|
||
emits('save-payment', paymentData);
|
||
resetForm();
|
||
closeModal();
|
||
};
|
||
|
||
// Закрытие окна и сброс формы
|
||
const closeModal = () => {
|
||
emits('close-modal');
|
||
resetForm();
|
||
};
|
||
|
||
const resetForm = () => {
|
||
name.value = '';
|
||
selectedCategory.value = props.expenseCategories[0];
|
||
description.value = '';
|
||
repeatDay.value = null;
|
||
amount.value = null;
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* Плавная анимация поворота */
|
||
.rotate-90 {
|
||
transform: rotate(90deg);
|
||
}
|
||
|
||
/* Для анимации открытия высоты */
|
||
.overflow-hidden {
|
||
overflow: hidden;
|
||
}
|
||
</style>
|