This commit is contained in:
Vladimir Voronin
2024-11-04 17:41:35 +03:00
parent c2b8787ed4
commit 7ae38604d2
11 changed files with 168 additions and 82 deletions

View File

@@ -10,18 +10,16 @@
<Button label="Sub" :class="checkNotif ? 'flex' : '!hidden'" @click="checkSubscribe"/>
<router-view/>
</div>
<OverlayView class="w-full sticky invisible lg:visible top-0 z-10"/>
<!-- <OverlayView class="w-full sticky invisible lg:visible top-0 z-10"/>-->
</div>
</template>
<script setup lang="ts">
import MenuBar from "./components/MenuBar.vue";
import OverlayView from "@/components/OverlayView.vue";
import ToolBar from "@/components/ToolBar.vue";
import Button from "primevue/button";
import axiosSetup from "@/services/axiosSetup";
import {computed, onMounted, ref} from "vue";
import {computed, onMounted} from "vue";
import {subscribeUserToPush} from "@/services/pushManager";
import apiClient from '@/services/axiosSetup';

View File

@@ -17,6 +17,8 @@
height: 0.5rem !important;
}
canvas {
/*margin-top: 1rem;*/
/*height: 12 8px !important*/

View File

@@ -42,18 +42,18 @@ import Menubar from "primevue/menubar";
const items = ref([
{
label: 'Home',
label: 'Главная',
icon: 'pi pi-home',
url: '/'
},
{
label: 'Analytics',
label: 'Аналитика',
icon: 'pi pi-star',
url: '/analytics'
},
{
label: 'Budgets',
icon: 'pi pi-search',
label: 'Бюджеты',
icon: 'pi pi-briefcase',
url: '/budgets'
// items: [
// {
@@ -93,12 +93,12 @@ const items = ref([
// ]
},
{
label: 'Transactions',
label: 'Транзакции',
icon: "pi pi-star",
url: '/transactions'
},
{
label: 'Settings',
label: 'Настройки',
icon: 'pi pi-envelope',
url: '/settings',
// badge: 3

View File

@@ -28,6 +28,7 @@ const openDrawer = (selectedTransactionType = null, selectedCategoryType = null)
}
const closeDrawer = () => {
drawerOpened.value = false;
}
@@ -76,11 +77,8 @@ onMounted(() => {
<div v-if="loading">Loding...</div>
<div v-else>
<TransactionEditDrawer v-if="drawerOpened" :visible="drawerOpened"
:transaction-type="transactionType"
:category-type="categoryType"
@close-drawer="closeDrawer()"
/>

View File

@@ -63,24 +63,32 @@ watch(
<button v-if="!isEditing" @click="startEditing"
class="text-lg font-bold cursor-pointer w-fit text-end line-clamp-1">
<div class="flex flex-row gap-2 items-baseline">
<p class="font-light text-sm" :class="spentPlannedRatio == 0 ? 'hidden': ''">{{spentPlannedRatio.toFixed(0)}} %</p>
<p class="font-light text-sm" :class="spentPlannedRatio == 0 ? 'hidden': ''">
{{ spentPlannedRatio.toFixed(0) }} %</p>
<p class="line-clamp-1 w-fit">{{ formatAmount(props.category.currentSpent) }} /
{{ formatAmount(currentLimit) }} </p>
</div>
</button>
<InputNumber v-else ref="inputRefs" type="text" v-model="props.category.currentLimit"
class="text-lg font-bold border-b-2 border-gray-300 outline-none focus:border-blue-500 w-32 text-right"
:min="props.category.categoryPlannedLimit" :max="900000" :invalid="currentLimit < props.category.categoryPlannedLimit"
class="text-lg font-bold border-b-2 border-gray-300 outline-none focus:border-none !w-24 text-right"
:min="props.category.categoryPlannedLimit" :max="900000"
:invalid="currentLimit < props.category.categoryPlannedLimit"
v-tooltip.top="'Сумма не должна быть ниже суммы запланированных!'"
unstyled />
unstyled
/>
<Button v-if="isEditing" @click="stopEditing" icon="pi pi-check" severity="success" rounded outlined
aria-label="Search"/>
</div>
</div>
<div class="flex flex-col w-full">
<ProgressBar :value="Number(spentPlannedRatio.toFixed(0))" class="w-full" :show-value="false"> </ProgressBar>
<ProgressBar :value="Number(spentPlannedRatio.toFixed(0))" class="w-full .your-parent-class"
:pt=" {
value: {
style: spentPlannedRatio >=100 ?'background: red !important' : '',
}
}" :color="'red-200'" :show-value="false"></ProgressBar>
<!-- <div class="z-50">{{formatAmount(spentPlannedRatio.toFixed(0))}}%</div>-->
</div>

View File

@@ -7,7 +7,7 @@ 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 {updateTransactionRequest} from "@/services/transactionService";
import {setTransactionDoneRequest} from "@/services/transactionService";
import {formatAmount, formatDate} from "@/utils/utils";
@@ -20,15 +20,19 @@ const props = defineProps(
isList: {
type: Boolean,
required: true,
},
transactions: {
type: Array as () => Array<Transaction>,
required: false
}
}
)
const emits = defineEmits(['open-drawer', 'transaction-checked', 'transaction-updated'])
const emits = defineEmits(['open-drawer', 'transaction-checked', 'transaction-updated', 'delete-transaction'])
const setIsDoneTrue = async () => {
setTimeout(async () => {
await updateTransactionRequest(props.transaction)
await setTransactionDoneRequest(props.transaction)
emits('transaction-checked')
}, 10);
// showedTransaction.value.isDone = !showedTransaction.value.isDone;
@@ -113,7 +117,8 @@ onMounted(async () => {
<p :class="transaction.isDone && isPlanned && !props.isList ? 'line-through' : ''" class="font-bold">{{
transaction.comment
}}</p>
<p :class="transaction.isDone && isPlanned && !props.isList ? 'line-through' : ''" class="font-light">{{
<p :class="transaction.isDone && isPlanned && !props.isList ? 'line-through' : ''" class="font-light">
{{ isPlanned ? transaction.category.icon : '' }} {{
transaction.category.name
}} |
{{ formatDate(transaction.date) }}</p>
@@ -128,13 +133,18 @@ onMounted(async () => {
</div>
</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()"
/>
</div>
</template>
<style scoped>

View File

@@ -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">
<!-- Аналитика и плановые доходы/расходы -->
@@ -49,7 +49,8 @@
<button class="grid grid-cols-2 gap-5 items-center w-full" @click="detailedShowed = !detailedShowed">
<div class="flex flex-col items-center font-bold ">
<h4 class="text-lg font-bold">Поступления</h4>
<div class="font-light bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
+{{ formatAmount(totalIncomes) }}
</div>
@@ -57,8 +58,8 @@
</div>
<div class="flex flex-col items-center ">
<h4 class="text-lg font-bold ">Расходы</h4>
<div class="font-light bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
-{{ formatAmount(totalExpenses) }}
<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>
@@ -107,7 +108,7 @@
</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">
<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>
@@ -133,8 +134,12 @@
</div>
<div class=" h-full overflow-y-auto gap-4 flex-col row-span-6 hidden lg:flex">
<div class="flex flex-row ">
<div class="flex flex-row gap-4">
<h3 class="text-2xl font-bold">Транзакции</h3>
<button @click="openDrawer('INSTANT', 'EXPENSE')">
<!-- <i class="pi pi-plus-circle text-green-500" style="font-size: 1rem;"/>-->
<span class="font-light text-sm">+ Добавить</span>
</button>
</div>
<div class=" flex gap-2 overflow-x-auto ">
@@ -147,7 +152,8 @@
<div class="grid grid-cols-1 gap-4 max-h-tlist overflow-y-auto pe-2">
<BudgetTransactionView v-for="transaction in transactions" :key="transaction.id"
:transaction="transaction"
:is-list="true" class=""
:is-list="true"
@transaction-updated="updateTransactions"
/>
</div>
</div>
@@ -158,9 +164,12 @@
<div class="card p-4 shadow-lg rounded-lg col-span-1 h-fit">
<!-- Планируемые доходы -->
<div>
<div class="flex flex-row gap-4 items-center">
<h3 class="text-xl font-bold text-green-500 mb-4 ">Плановые поступления</h3>
<!-- <Button icon="pi pi-plus" rounded outlined size="small"/>-->
<div class="flex flex-row gap-4 items-center mb-4">
<h3 class="text-xl font-bold text-green-500 ">Плановые поступления</h3>
<button @click="openDrawer('PLANNED', 'INCOME')">
<!-- <i class="pi pi-plus-circle text-green-500" style="font-size: 1rem;"/>-->
<span class="font-light text-sm">+ Добавить</span>
</button>
</div>
<div class="grid grid-cols-2 mb-2">
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
@@ -185,7 +194,13 @@
<div class="card p-4 shadow-lg rounded-lg col-span-1 h-fit">
<!-- Планируемые расходы -->
<div class>
<h3 class="text-xl font-bold text-red-500 mb-4">Плановые расходы</h3>
<div class="flex flex-row gap-4 items-center mb-4">
<h3 class="text-xl font-bold text-red-500">Плановые расходы</h3>
<button @click="openDrawer('PLANNED', 'EXPENSE')">
<!-- <i class="pi pi-plus-circle text-green-500" style="font-size: 1rem;"/>-->
<span class="font-light text-sm">+ Добавить</span>
</button>
</div>
<div class="grid grid-cols-2 mb-2">
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
{{ formatAmount(totalPlannedExpenses) }}
@@ -219,7 +234,11 @@
</div>
<div class=" h-full overflow-y-auto gap-4 flex-col row-span-6 lg:hidden ">
<div class="flex flex-row ">
<h3 class="text-2xl font-bold">Транзакций</h3>
<h3 class="text-2xl font-bold">Транзакции</h3>
<button @click="openDrawer('INSTANT', 'EXPENSE')">
<!-- <i class="pi pi-plus-circle text-green-500" style="font-size: 1rem;"/>-->
<span class="font-light text-sm">+ Добавить</span>
</button>
</div>
<div class=" flex gap-2">
<button v-for="categorySum in transactionCategoriesSums"
@@ -231,7 +250,9 @@
<div class="grid grid-cols-1 gap-4 max-h-tlist overflow-y-auto pe-2">
<BudgetTransactionView v-for="transaction in transactions" :key="transaction.id"
:transaction="transaction"
:is-list="true" class=""
:is-list="true"
@transaction-updated="updateTransactions"
/>
</div>
</div>
@@ -240,7 +261,18 @@
</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"
/>
</div>
</template>
@@ -256,7 +288,7 @@ import {
} from "@/services/budgetsService";
import {Budget, BudgetCategory, BudgetInfo} from "@/models/Budget";
import {useRoute} from "vue-router";
import {formatAmount} from "@/utils/utils";
import {formatAmount, formatDate} from "@/utils/utils";
import ProgressBar from "primevue/progressbar";
import ProgressSpinner from "primevue/progressspinner";
import BudgetCategoryView from "@/components/budgets/BudgetCategoryView.vue";
@@ -267,6 +299,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";
// Зарегистрируем плагин
ChartJS.register(ChartDataLabels);
@@ -334,7 +367,26 @@ const totalSaving = computed(() => {
return value
})
//
const drawerOpened = ref(false)
const transactionType = ref('')
const categoryType = ref('')
const openDrawer = (selectedTransactionType = null, selectedCategoryType = null) => {
if (selectedTransactionType && selectedCategoryType) {
transactionType.value = selectedTransactionType;
categoryType.value = selectedCategoryType;
} else if (selectedTransactionType) {
transactionType.value = selectedTransactionType;
categoryType.value = 'EXPENSE'
}
drawerOpened.value = true;
}
const closeDrawer = async () => {
drawerOpened.value = false;
// await updateTransactions()
}
const dailyRatio = computed(() => {
const value = (totalExpenses.value - totalLoans.value - totalSaving.value) / totalExpenses.value
@@ -384,15 +436,12 @@ const fetchPlannedExpenses = async () => {
}
const transactions = ref<Transaction[]>([])
const fetchBudgetTransactions = async () => {
transactions.value = await getBudgetTransactions(route.params.id, 'INSTANT')
updateLoading.value = false
}
const updateTransactions = async () => {
await Promise.all([fetchPlannedIncomes(), fetchPlannedExpenses(), fetchBudgetCategories(),])
await Promise.all([fetchPlannedIncomes(), fetchPlannedExpenses(), fetchBudgetCategories(), fetchBudgetTransactions()])
}
const categories = ref<BudgetCategory[]>([])
@@ -480,8 +529,9 @@ const incomesByPeriod = computed(() => {
let incomesUntil25 = 0
let incomesFrom25 = 0
plannedIncomes.value.forEach((i) => {
if (i.date <= budget.value?.dateFrom && i.date <= twentyFour.value) {
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

View File

@@ -15,11 +15,12 @@ import {
createTransactionRequest,
getTransactionTypes,
updateTransactionRequest,
deleteTransactionRequest
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";
const props = defineProps({
visible: {
@@ -37,6 +38,10 @@ const props = defineProps({
categoryType: {
type: String,
required: false
},
transactions: {
type: Array as () => Array<Transaction>,
required: false
}
});
@@ -247,6 +252,7 @@ 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;
@@ -254,7 +260,12 @@ onMounted(async () => {
await fetchCategoriesAndTypes();
prepareData();
if (!transactions.value && !isEditing.value) {
console.log()
await getTransactions('INSTANT', 'EXPENSE' ).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';
@@ -267,7 +278,7 @@ onMounted(async () => {
<div class="card flex justify-center h-dvh">
<Drawer :visible="visible" :header="isEditing ? 'Edit Transaction' : 'Create Transaction'" :showCloseIcon="false"
<Drawer :visible="visible" :header="isEditing ? 'Изменить транзакцию' : 'Создать транзакцию'" :showCloseIcon="false"
position="right" @hide="closeDrawer"
class="!w-128 ">
<div v-if="result" class="absolute top-0 left-0 w-full h-full flex items-center justify-center z-50">
@@ -363,13 +374,13 @@ onMounted(async () => {
@blur="keyboardOpen=false"
/>
<label for="amount" class="">Amount</label>
<label for="amount" class="">Сумма</label>
</FloatLabel>
<!-- Comment Input -->
<FloatLabel variant="on" class="w-full">
<label for="comment">Comment</label>
<label for="comment">Комментарий</label>
<InputText class="w-full"
:invalid="!editedTransaction.comment"
id="comment"
@@ -384,7 +395,7 @@ onMounted(async () => {
<!-- Date Picker -->
<div class="field col-12 gap-0">
<FloatLabel variant="on">
<label for="date">Date </label>
<label for="date">Дата</label>
<DatePicker class="w-full"
inline
@@ -401,18 +412,17 @@ onMounted(async () => {
</FloatLabel>
</div>
<div>
<BudgetTransactionView v-if="!isEditing && transactions" v-for="transaction in transactions" :is-list="true"
:transaction="transaction"/>
</div>
<!-- Amount Input -->
<!-- Buttons -->
{{ keyboardOpen }}
<div class="fixed col-12 flex justify-content-end gap-4 bottom-8">
<Button label="Save" icon="pi pi-check" class="p-button-success"
<Button label="Сохранить" 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"
<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>

View File

@@ -28,7 +28,7 @@ export const getBudgetTransactions = async (budgetId, transactionType, categoryT
let url = `/budgets/${budgetId}/transactions`
if (transactionType && !categoryType) {
url += '/?type=' + transactionType
url += '?type=' + transactionType
}
if (transactionType && categoryType) {
url += '/'+transactionType+'/'+categoryType

View File

@@ -9,7 +9,7 @@ export const getCategories = async (type = null) => {
};
export const getCategoryTypes = async () => {
return await apiClient.get('/categories/types/');
return await apiClient.get('/categories/types');
}
export const createCategory = async (category: Category) => {

View File

@@ -36,7 +36,7 @@ export const createTransactionRequest = async (transaction: Transaction) => {
export const updateTransactionRequest = async (transaction: Transaction) => {
const id = transaction.id
// transaction.date = format(transaction.date, 'yyyy-MM-dd')
transaction.date = format(transaction.date, 'yyyy-MM-dd')
const response = await apiClient.put(`/transactions/${id}`, transaction);
transaction = response.data
transaction.date = new Date(transaction.date);
@@ -44,10 +44,20 @@ export const updateTransactionRequest = async (transaction: Transaction) => {
return transaction
};
export const setTransactionDoneRequest = async (transaction: Transaction) => {
const id = transaction.id
// transaction.date = format(transaction.date, 'yyyy-MM-dd')
const response = await apiClient.put(`/transactions/${id}/done`, transaction);
transaction = response.data
transaction.date = new Date(transaction.date);
return transaction
};
export const deleteTransactionRequest = async (id: number) => {
return await apiClient.delete(`/transactions/${id}`);
};
export const getTransactionTypes = async () => {
return await apiClient.get('/transactions/types/');
return await apiClient.get('/transactions/types');
}