chet novoe
This commit is contained in:
@@ -4,6 +4,10 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
.p-inputnumber-input{
|
||||||
|
width: 7rem;
|
||||||
|
}
|
||||||
|
|
||||||
/*#app {*/
|
/*#app {*/
|
||||||
/* !*max-width: 1280px;*!*/
|
/* !*max-width: 1280px;*!*/
|
||||||
/* !*margin: 0 auto;*!*/
|
/* !*margin: 0 auto;*!*/
|
||||||
|
|||||||
28
src/components/LoadingView.vue
Normal file
28
src/components/LoadingView.vue
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import ProgressSpinner from "primevue/progressspinner";
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="relative w-full h-screen">
|
||||||
|
<!-- Полупрозрачный белый фон -->
|
||||||
|
<div class="absolute top-0 left-0 w-full h-full bg-white opacity-50 z-0"></div>
|
||||||
|
|
||||||
|
<!-- Спиннер поверх -->
|
||||||
|
<div class="absolute top-0 left-0 w-full h-full flex items-center justify-center z-50">
|
||||||
|
<ProgressSpinner
|
||||||
|
style="width: 50px; height: 50px;"
|
||||||
|
strokeWidth="8"
|
||||||
|
fill="transparent"
|
||||||
|
animationDuration=".5s"
|
||||||
|
aria-label="Custom ProgressSpinner"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,28 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="px-4 bg-gray-100 h-full ">
|
<LoadingView v-if="loading"/>
|
||||||
|
<div v-else class="px-4 bg-gray-100 h-full ">
|
||||||
<!-- Заголовок -->
|
<!-- Заголовок -->
|
||||||
<h2 class="text-4xl mb-6 font-bold">Monthly Budgets</h2>
|
<h2 class="text-4xl mb-6 font-bold">Monthly Budgets</h2>
|
||||||
|
|
||||||
<!-- Плитка с бюджетами -->
|
<!-- Плитка с бюджетами -->
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
<!-- Будущие и текущие бюджеты -->
|
<!-- Будущие и текущие бюджеты -->
|
||||||
<div v-for="budget in upcomingBudgets" :key="budget.id" class="p-4 shadow-lg rounded-lg bg-white">
|
<div v-for="budget in budgetInfos" :key="budget.budget.id" class="p-4 shadow-lg rounded-lg bg-white" :class="budget.budget.dateTo < new Date() ? 'bg-gray-100 opacity-60' : ''">
|
||||||
<div class="flex flex-row justify-between">
|
<div class="flex flex-row justify-between">
|
||||||
<div class="text-xl font-bold mb-2">{{ budget.month }}</div>
|
<div class="text-xl font-bold mb-2">{{ budget.budget.name }}</div>
|
||||||
<router-link to="/budgets/1">
|
<router-link :to="'/budgets/'+budget.budget.id">
|
||||||
<Button icon="pi pi-arrow-circle-right" rounded text size="large"/>
|
<i class="pi pi-arrow-circle-right text-green-500" style="font-size: 1.5rem;"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-gray-600 mb-4">
|
<div class="text-sm text-gray-600 mb-4">
|
||||||
{{ budget.startDate }} - {{ budget.endDate }}
|
{{ formatDate(budget.budget.dateFrom) }} - {{ formatDate(budget.budget.dateTo) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<div class="text-sm">Total Income: <span class="font-bold">{{ budget.totalIncome }}</span></div>
|
<div class="text-sm">Total Income: <span class="font-bold">{{ formatAmount(budget.totalIncomes) }} ₽</span></div>
|
||||||
<div class="text-sm">Total Expenses: <span class="font-bold">{{ budget.totalExpenses }}</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">{{ budget.plannedExpenses }}</span></div>
|
<div class="text-sm">Planned Expenses: <span class="font-bold">{{ formatAmount(budget.totalExpenses) }} ₽</span></div>
|
||||||
<div class="text-sm flex items-center">
|
<div class="text-sm flex items-center">
|
||||||
Unplanned Expenses:
|
Unplanned Expenses:
|
||||||
<span class="ml-2 font-bold">{{ budget.remainingForUnplanned }}</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>
|
||||||
@@ -51,73 +52,72 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref} from 'vue';
|
import {onMounted, ref} from 'vue';
|
||||||
import ProgressBar from 'primevue/progressbar';
|
import ProgressBar from 'primevue/progressbar';
|
||||||
import Button from "primevue/button";
|
import {BudgetInfo} from "@/models/Budget";
|
||||||
|
import {getBudgetInfos} from "@/services/budgetsService";
|
||||||
|
import {formatAmount, formatDate} from "@/utils/utils";
|
||||||
|
import LoadingView from "@/components/LoadingView.vue";
|
||||||
|
|
||||||
export default {
|
const loading = ref(false)
|
||||||
components: {
|
const budgetInfos = ref<BudgetInfo[]>([])
|
||||||
ProgressBar,
|
|
||||||
Button
|
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, // Прогресс в процентах
|
||||||
},
|
},
|
||||||
setup() {
|
{
|
||||||
const upcomingBudgets = ref([
|
id: 2,
|
||||||
{
|
month: 'November 2024',
|
||||||
id: 1,
|
startDate: '2024-11-01',
|
||||||
month: 'October 2024',
|
endDate: '2024-11-30',
|
||||||
startDate: '2024-10-01',
|
totalIncome: '550,000 RUB',
|
||||||
endDate: '2024-10-31',
|
totalExpenses: '320,000 RUB',
|
||||||
totalIncome: '500,000 RUB',
|
plannedExpenses: '250,000 RUB',
|
||||||
totalExpenses: '350,000 RUB',
|
remainingForUnplanned: '70,000 RUB',
|
||||||
plannedExpenses: '300,000 RUB',
|
unplannedProgress: 50,
|
||||||
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 pastBudgets = ref([
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
month: 'September 2024',
|
|
||||||
startDate: '2024-09-01',
|
|
||||||
endDate: '2024-09-30',
|
|
||||||
totalIncome: '450,000 RUB',
|
|
||||||
totalExpenses: '400,000 RUB',
|
|
||||||
plannedExpenses: '350,000 RUB',
|
|
||||||
remainingForUnplanned: '50,000 RUB',
|
|
||||||
unplannedProgress: 90,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
month: 'August 2024',
|
|
||||||
startDate: '2024-08-01',
|
|
||||||
endDate: '2024-08-31',
|
|
||||||
totalIncome: '400,000 RUB',
|
|
||||||
totalExpenses: '370,000 RUB',
|
|
||||||
plannedExpenses: '320,000 RUB',
|
|
||||||
remainingForUnplanned: '50,000 RUB',
|
|
||||||
unplannedProgress: 85,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
upcomingBudgets,
|
|
||||||
pastBudgets,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
};
|
]);
|
||||||
|
const pastBudgets = ref([
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
month: 'September 2024',
|
||||||
|
startDate: '2024-09-01',
|
||||||
|
endDate: '2024-09-30',
|
||||||
|
totalIncome: '450,000 RUB',
|
||||||
|
totalExpenses: '400,000 RUB',
|
||||||
|
plannedExpenses: '350,000 RUB',
|
||||||
|
remainingForUnplanned: '50,000 RUB',
|
||||||
|
unplannedProgress: 90,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
month: 'August 2024',
|
||||||
|
startDate: '2024-08-01',
|
||||||
|
endDate: '2024-08-31',
|
||||||
|
totalIncome: '400,000 RUB',
|
||||||
|
totalExpenses: '370,000 RUB',
|
||||||
|
plannedExpenses: '320,000 RUB',
|
||||||
|
remainingForUnplanned: '50,000 RUB',
|
||||||
|
unplannedProgress: 85,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
budgetInfos.value = await getBudgetInfos()
|
||||||
|
console.log(budgetInfos.value)
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,20 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<Toast/>
|
<Toast/>
|
||||||
<div v-if="loading" class="relative w-full h-screen">
|
<LoadingView v-if="loading"/>
|
||||||
<!-- Полупрозрачный белый фон -->
|
|
||||||
<div class="absolute top-0 left-0 w-full h-full bg-white opacity-50 z-0"></div>
|
|
||||||
|
|
||||||
<!-- Спиннер поверх -->
|
|
||||||
<div class="absolute top-0 left-0 w-full h-full flex items-center justify-center z-50">
|
|
||||||
<ProgressSpinner
|
|
||||||
style="width: 50px; height: 50px;"
|
|
||||||
strokeWidth="8"
|
|
||||||
fill="transparent"
|
|
||||||
animationDuration=".5s"
|
|
||||||
aria-label="Custom ProgressSpinner"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="px-4 bg-gray-100 h-full ">
|
<div v-else class="px-4 bg-gray-100 h-full ">
|
||||||
<div v-if="updateLoading" class="absolute top-0 left-0 w-full h-full flex items-center justify-center z-50">
|
<div v-if="updateLoading" class="absolute top-0 left-0 w-full h-full flex items-center justify-center z-50">
|
||||||
@@ -216,6 +202,7 @@ import UnplannedCategoryView from "@/components/budgets/UnplannedCategoryView.vu
|
|||||||
import {TransactionType} from "@/models/Transaction";
|
import {TransactionType} from "@/models/Transaction";
|
||||||
import Toast from "primevue/toast";
|
import Toast from "primevue/toast";
|
||||||
import Button from "primevue/button";
|
import Button from "primevue/button";
|
||||||
|
import LoadingView from "@/components/LoadingView.vue";
|
||||||
|
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const updateLoading = ref(false);
|
const updateLoading = ref(false);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
} from "@/services/transactionService";
|
} from "@/services/transactionService";
|
||||||
import {getCategories, getCategoryTypes} from "@/services/categoryService";
|
import {getCategories, getCategoryTypes} from "@/services/categoryService";
|
||||||
import {useToast} from "primevue/usetoast";
|
import {useToast} from "primevue/usetoast";
|
||||||
|
import LoadingView from "@/components/LoadingView.vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: {
|
visible: {
|
||||||
@@ -192,9 +193,7 @@ onMounted(async () => {
|
|||||||
<Drawer :visible="visible" :header="isEditing ? 'Edit Transaction' : 'Create Transaction'" :showCloseIcon="false"
|
<Drawer :visible="visible" :header="isEditing ? 'Edit Transaction' : 'Create Transaction'" :showCloseIcon="false"
|
||||||
position="right" @hide="closeDrawer"
|
position="right" @hide="closeDrawer"
|
||||||
class="!w-128 ">
|
class="!w-128 ">
|
||||||
<div v-if="loading">
|
<LoadingView v-if="loading"/>
|
||||||
Loading...
|
|
||||||
</div>
|
|
||||||
<div v-else class=" grid gap-4 w-full ">
|
<div v-else class=" grid gap-4 w-full ">
|
||||||
|
|
||||||
<div class="relative w-full justify-center justify-items-center ">
|
<div class="relative w-full justify-center justify-items-center ">
|
||||||
@@ -253,11 +252,11 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-4">
|
<div class="flex flex-row gap-4">
|
||||||
<div class=" ">
|
|
||||||
|
|
||||||
<FloatLabel variant="on" class="w-10">
|
|
||||||
<InputNumber class="!w-10"
|
<FloatLabel variant="on" class="">
|
||||||
|
<InputNumber class=""
|
||||||
:invalid="!editedTransaction.amount"
|
:invalid="!editedTransaction.amount"
|
||||||
:minFractionDigits="0"
|
:minFractionDigits="0"
|
||||||
id="amount"
|
id="amount"
|
||||||
@@ -267,12 +266,12 @@ onMounted(async () => {
|
|||||||
locale="ru-RU"
|
locale="ru-RU"
|
||||||
|
|
||||||
/>
|
/>
|
||||||
<label for="amount" class="!w-10">Amount</label>
|
<label for="amount" class="">Amount</label>
|
||||||
</FloatLabel>
|
</FloatLabel>
|
||||||
</div>
|
|
||||||
<!-- Comment Input -->
|
<!-- Comment Input -->
|
||||||
<div class="field col-12 col-span-3">
|
|
||||||
<FloatLabel variant="on" class="l">
|
<FloatLabel variant="on" class="w-full">
|
||||||
<label for="comment">Comment</label>
|
<label for="comment">Comment</label>
|
||||||
<InputText class="w-full"
|
<InputText class="w-full"
|
||||||
:invalid="!editedTransaction.comment"
|
:invalid="!editedTransaction.comment"
|
||||||
@@ -281,7 +280,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
/>
|
/>
|
||||||
</FloatLabel>
|
</FloatLabel>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Date Picker -->
|
<!-- Date Picker -->
|
||||||
@@ -309,7 +308,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
|
|
||||||
<!-- Buttons -->
|
<!-- Buttons -->
|
||||||
<div class="field col-12 flex justify-content-end gap-4">
|
<div class="fixed col-12 bottom-6 flex justify-content-end gap-4">
|
||||||
|
|
||||||
<Button label="Save" icon="pi pi-check" class="p-button-success"
|
<Button label="Save" icon="pi pi-check" class="p-button-success"
|
||||||
@click="isEditing ? updateTransaction() : createTransaction()"/>
|
@click="isEditing ? updateTransaction() : createTransaction()"/>
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { useRouter } from 'vue-router';
|
|||||||
|
|
||||||
// Создаем экземпляр axios
|
// Создаем экземпляр axios
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: 'https://luminic.space/api/v1',
|
// baseURL: 'https://luminic.space/api/v1',
|
||||||
|
baseURL: 'http://localhost:8000/api/v1',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Устанавливаем токен из localStorage при каждом запуске
|
// Устанавливаем токен из localStorage при каждом запуске
|
||||||
|
|||||||
@@ -1,7 +1,29 @@
|
|||||||
import apiClient from '@/services/axiosSetup';
|
import apiClient from '@/services/axiosSetup';
|
||||||
import {BudgetCategory} from "@/models/Budget";
|
import {Budget, BudgetCategory} from "@/models/Budget";
|
||||||
// Импортируете настроенный экземпляр axios
|
// Импортируете настроенный экземпляр axios
|
||||||
|
|
||||||
|
export const getBudgetInfos = async () => {
|
||||||
|
console.log('getBudgetInfos');
|
||||||
|
let response = await apiClient.get('/budgets/');
|
||||||
|
let budgetInfos = response.data;
|
||||||
|
budgetInfos.forEach((budgetInfo: Budget) => {
|
||||||
|
budgetInfo.budget.dateFrom = new Date(budgetInfo.budget.dateFrom);
|
||||||
|
budgetInfo.budget.dateTo = new Date(budgetInfo.budget.dateTo);
|
||||||
|
budgetInfo.plannedExpenses.forEach(e => {
|
||||||
|
e.date = new Date(e.date)
|
||||||
|
})
|
||||||
|
|
||||||
|
budgetInfo.plannedIncomes.forEach(e => {
|
||||||
|
e.date = new Date(e.date)
|
||||||
|
})
|
||||||
|
|
||||||
|
budgetInfo.transactions.forEach(e => {
|
||||||
|
e.date = new Date(e.date)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return budgetInfos
|
||||||
|
}
|
||||||
|
|
||||||
export const getBudgetInfo = async (budget_id: number) => {
|
export const getBudgetInfo = async (budget_id: number) => {
|
||||||
console.log('getBudgetInfo');
|
console.log('getBudgetInfo');
|
||||||
let budgetInfo = await apiClient.get('/budgets/' + budget_id);
|
let budgetInfo = await apiClient.get('/budgets/' + budget_id);
|
||||||
|
|||||||
Reference in New Issue
Block a user