Revert "Merge pull request 'Завершить аналитику расходов и доходов' (#35) from feature/analytics-completion into main"
This reverts commit66c04b0618, reversing changes made toefc9854064.
This commit is contained in:
@@ -1,11 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [Frontend 0.11.0 / Backend 0.10.0 / Shared 0.5.0] - 2026-08-21
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Added category filtering to analytics summary and category breakdown, including savings-account interest handling in the filtered results.
|
|
||||||
|
|
||||||
## [Backend 0.9.2] - 2026-08-20
|
## [Backend 0.9.2] - 2026-08-20
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@family-budget/backend",
|
"name": "@family-budget/backend",
|
||||||
"version": "0.10.0",
|
"version": "0.9.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx watch src/app.ts",
|
"dev": "tsx watch src/app.ts",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const router = Router();
|
|||||||
router.get(
|
router.get(
|
||||||
'/summary',
|
'/summary',
|
||||||
asyncHandler(async (req, res) => {
|
asyncHandler(async (req, res) => {
|
||||||
const { from, to, accountId, categoryId, onlyConfirmed } = req.query;
|
const { from, to, accountId, onlyConfirmed } = req.query;
|
||||||
if (!from || !to) {
|
if (!from || !to) {
|
||||||
res.status(400).json({ error: 'BAD_REQUEST', message: 'from and to are required' });
|
res.status(400).json({ error: 'BAD_REQUEST', message: 'from and to are required' });
|
||||||
return;
|
return;
|
||||||
@@ -18,7 +18,6 @@ router.get(
|
|||||||
from: from as string,
|
from: from as string,
|
||||||
to: to as string,
|
to: to as string,
|
||||||
accountId: accountId ? Number(accountId) : undefined,
|
accountId: accountId ? Number(accountId) : undefined,
|
||||||
categoryId: categoryId ? Number(categoryId) : undefined,
|
|
||||||
onlyConfirmed: onlyConfirmed === 'true',
|
onlyConfirmed: onlyConfirmed === 'true',
|
||||||
});
|
});
|
||||||
res.json(result);
|
res.json(result);
|
||||||
@@ -28,7 +27,7 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
'/by-category',
|
'/by-category',
|
||||||
asyncHandler(async (req, res) => {
|
asyncHandler(async (req, res) => {
|
||||||
const { from, to, accountId, categoryId, onlyConfirmed } = req.query;
|
const { from, to, accountId, onlyConfirmed } = req.query;
|
||||||
if (!from || !to) {
|
if (!from || !to) {
|
||||||
res.status(400).json({ error: 'BAD_REQUEST', message: 'from and to are required' });
|
res.status(400).json({ error: 'BAD_REQUEST', message: 'from and to are required' });
|
||||||
return;
|
return;
|
||||||
@@ -38,7 +37,6 @@ router.get(
|
|||||||
from: from as string,
|
from: from as string,
|
||||||
to: to as string,
|
to: to as string,
|
||||||
accountId: accountId ? Number(accountId) : undefined,
|
accountId: accountId ? Number(accountId) : undefined,
|
||||||
categoryId: categoryId ? Number(categoryId) : undefined,
|
|
||||||
onlyConfirmed: onlyConfirmed === 'true',
|
onlyConfirmed: onlyConfirmed === 'true',
|
||||||
});
|
});
|
||||||
res.json(result);
|
res.json(result);
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ async function testQueries(): Promise<void> {
|
|||||||
const params = { from: '2026-07-01', to: '2026-07-31', accountId, onlyConfirmed: true };
|
const params = { from: '2026-07-01', to: '2026-07-31', accountId, onlyConfirmed: true };
|
||||||
const summary = await getSummary(params, client);
|
const summary = await getSummary(params, client);
|
||||||
assert.deepEqual({ expense: summary.totalExpense, income: summary.totalIncome, net: summary.net, transferOut: summary.transferOutflow, interest: summary.interestIncome }, { expense: 6_000, income: 15_000, net: 9_000, transferOut: 3_000, interest: 1_500 });
|
assert.deepEqual({ expense: summary.totalExpense, income: summary.totalIncome, net: summary.net, transferOut: summary.transferOutflow, interest: summary.interestIncome }, { expense: 6_000, income: 15_000, net: 9_000, transferOut: 3_000, interest: 1_500 });
|
||||||
const categorySummary = await getSummary({ ...params, categoryId: categoryId.expense }, client);
|
|
||||||
assert.deepEqual({ expense: categorySummary.totalExpense, income: categorySummary.totalIncome }, { expense: 6_000, income: 0 });
|
|
||||||
assert.equal((await getSummary({ ...params, from: '2026-08-01', to: '2026-08-31' }, client)).totalExpense, 7_000);
|
assert.equal((await getSummary({ ...params, from: '2026-08-01', to: '2026-08-31' }, client)).totalExpense, 7_000);
|
||||||
assert.deepEqual((await getByCategory(params, client)).map((item) => item.amount), [6_000]);
|
assert.deepEqual((await getByCategory(params, client)).map((item) => item.amount), [6_000]);
|
||||||
const timeseries = await getTimeseries({ ...params, granularity: 'month' }, client);
|
const timeseries = await getTimeseries({ ...params, granularity: 'month' }, client);
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ interface BaseParams {
|
|||||||
from: string;
|
from: string;
|
||||||
to: string;
|
to: string;
|
||||||
accountId?: number;
|
accountId?: number;
|
||||||
categoryId?: number;
|
|
||||||
onlyConfirmed?: boolean;
|
onlyConfirmed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,11 +61,6 @@ function buildBaseConditions(
|
|||||||
values.push(params.accountId);
|
values.push(params.accountId);
|
||||||
idx++;
|
idx++;
|
||||||
}
|
}
|
||||||
if (params.categoryId != null) {
|
|
||||||
conditions.push(`t.category_id = $${idx}`);
|
|
||||||
values.push(params.categoryId);
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
if (params.onlyConfirmed) {
|
if (params.onlyConfirmed) {
|
||||||
conditions.push('t.is_category_confirmed = TRUE');
|
conditions.push('t.is_category_confirmed = TRUE');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@family-budget/frontend",
|
"name": "@family-budget/frontend",
|
||||||
"version": "0.11.0",
|
"version": "0.10.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import type {
|
import type {
|
||||||
Account,
|
Account,
|
||||||
Category,
|
|
||||||
AnalyticsSummaryResponse,
|
AnalyticsSummaryResponse,
|
||||||
ByCategoryItem,
|
ByCategoryItem,
|
||||||
TimeseriesItem,
|
TimeseriesItem,
|
||||||
Granularity,
|
Granularity,
|
||||||
} from '@family-budget/shared';
|
} from '@family-budget/shared';
|
||||||
import { getAccounts } from '../api/accounts';
|
import { getAccounts } from '../api/accounts';
|
||||||
import { getCategories } from '../api/categories';
|
|
||||||
import { getSummary, getByCategory, getTimeseries } from '../api/analytics';
|
import { getSummary, getByCategory, getTimeseries } from '../api/analytics';
|
||||||
import {
|
import {
|
||||||
PeriodSelector,
|
PeriodSelector,
|
||||||
@@ -31,10 +29,8 @@ function getDefaultPeriod(): PeriodState {
|
|||||||
export function AnalyticsPage() {
|
export function AnalyticsPage() {
|
||||||
const [period, setPeriod] = useState<PeriodState>(getDefaultPeriod);
|
const [period, setPeriod] = useState<PeriodState>(getDefaultPeriod);
|
||||||
const [accountId, setAccountId] = useState<string>('');
|
const [accountId, setAccountId] = useState<string>('');
|
||||||
const [categoryId, setCategoryId] = useState<string>('');
|
|
||||||
const [onlyConfirmed, setOnlyConfirmed] = useState(false);
|
const [onlyConfirmed, setOnlyConfirmed] = useState(false);
|
||||||
const [accounts, setAccounts] = useState<Account[]>([]);
|
const [accounts, setAccounts] = useState<Account[]>([]);
|
||||||
const [categories, setCategories] = useState<Category[]>([]);
|
|
||||||
const [summary, setSummary] = useState<AnalyticsSummaryResponse | null>(
|
const [summary, setSummary] = useState<AnalyticsSummaryResponse | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
@@ -44,7 +40,6 @@ export function AnalyticsPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getAccounts().then(setAccounts).catch(() => {});
|
getAccounts().then(setAccounts).catch(() => {});
|
||||||
getCategories({ isActive: true }).then(setCategories).catch(() => {});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchAll = useCallback(async () => {
|
const fetchAll = useCallback(async () => {
|
||||||
@@ -68,7 +63,6 @@ export function AnalyticsPage() {
|
|||||||
from: period.from,
|
from: period.from,
|
||||||
to: period.to,
|
to: period.to,
|
||||||
...(accountId ? { accountId: Number(accountId) } : {}),
|
...(accountId ? { accountId: Number(accountId) } : {}),
|
||||||
...(categoryId ? { categoryId: Number(categoryId) } : {}),
|
|
||||||
...(onlyConfirmed ? { onlyConfirmed: true } : {}),
|
...(onlyConfirmed ? { onlyConfirmed: true } : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -86,7 +80,7 @@ export function AnalyticsPage() {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [period, accountId, categoryId, onlyConfirmed]);
|
}, [period, accountId, onlyConfirmed]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchAll();
|
fetchAll();
|
||||||
@@ -118,13 +112,6 @@ export function AnalyticsPage() {
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="field">
|
|
||||||
<label className="field__label">Категория</label>
|
|
||||||
<select value={categoryId} onChange={(e) => setCategoryId(e.target.value)}>
|
|
||||||
<option value="">Все категории</option>
|
|
||||||
{categories.map((c) => <option key={c.id} value={c.id}>{c.name}</option>)}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="field field--checkbox">
|
<div className="field field--checkbox">
|
||||||
<label className="field__label field__label--checkbox">
|
<label className="field__label field__label--checkbox">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@family-budget/shared",
|
"name": "@family-budget/shared",
|
||||||
"version": "0.5.0",
|
"version": "0.4.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ export interface AnalyticsSummaryParams {
|
|||||||
from: string;
|
from: string;
|
||||||
to: string;
|
to: string;
|
||||||
accountId?: number;
|
accountId?: number;
|
||||||
categoryId?: number;
|
|
||||||
onlyConfirmed?: boolean;
|
onlyConfirmed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +31,6 @@ export interface ByCategoryParams {
|
|||||||
from: string;
|
from: string;
|
||||||
to: string;
|
to: string;
|
||||||
accountId?: number;
|
accountId?: number;
|
||||||
categoryId?: number;
|
|
||||||
onlyConfirmed?: boolean;
|
onlyConfirmed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user