mirror of
https://github.com/mvvasilev/personal-finances.git
synced 2025-04-19 14:19:52 +03:00
Fix to and from dates for statistics
This commit is contained in:
parent
51465ad44d
commit
a95fe9dcf4
5 changed files with 42 additions and 20 deletions
|
@ -7,6 +7,7 @@ import dev.mvvasilev.finances.dtos.SpendingOverTimeByCategoryDTO;
|
||||||
import dev.mvvasilev.finances.enums.TimePeriod;
|
import dev.mvvasilev.finances.enums.TimePeriod;
|
||||||
import dev.mvvasilev.finances.services.StatisticsService;
|
import dev.mvvasilev.finances.services.StatisticsService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
|
@ -38,8 +39,10 @@ public class StatisticsController extends AbstractRestController {
|
||||||
@PreAuthorize("@authService.isOwner(#categoryId, T(dev.mvvasilev.finances.entity.TransactionCategory))")
|
@PreAuthorize("@authService.isOwner(#categoryId, T(dev.mvvasilev.finances.entity.TransactionCategory))")
|
||||||
public ResponseEntity<APIResponseDTO<SpendingByCategoriesDTO>> fetchSpendingByCategory(
|
public ResponseEntity<APIResponseDTO<SpendingByCategoriesDTO>> fetchSpendingByCategory(
|
||||||
Long[] categoryId,
|
Long[] categoryId,
|
||||||
@RequestParam(defaultValue = "1970-01-01T00:00:00") LocalDateTime from,
|
@RequestParam(defaultValue = "1970-01-01T00:00:00Z")
|
||||||
@RequestParam(defaultValue = "2099-01-01T00:00:00") LocalDateTime to,
|
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime from,
|
||||||
|
@RequestParam(defaultValue = "2099-01-01T00:00:00Z")
|
||||||
|
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime to,
|
||||||
@RequestParam(defaultValue = "false") Boolean includeUncategorized
|
@RequestParam(defaultValue = "false") Boolean includeUncategorized
|
||||||
) {
|
) {
|
||||||
return ok(statisticsService.spendingByCategory(categoryId, from, to));
|
return ok(statisticsService.spendingByCategory(categoryId, from, to));
|
||||||
|
@ -50,8 +53,10 @@ public class StatisticsController extends AbstractRestController {
|
||||||
public ResponseEntity<APIResponseDTO<SpendingOverTimeByCategoryDTO>> fetchSpendingOverTimeByCategory(
|
public ResponseEntity<APIResponseDTO<SpendingOverTimeByCategoryDTO>> fetchSpendingOverTimeByCategory(
|
||||||
Long[] categoryId,
|
Long[] categoryId,
|
||||||
@RequestParam(defaultValue = "DAILY") TimePeriod period,
|
@RequestParam(defaultValue = "DAILY") TimePeriod period,
|
||||||
@RequestParam(defaultValue = "1970-01-01T00:00:00") LocalDateTime from,
|
@RequestParam(defaultValue = "1970-01-01T00:00:00Z")
|
||||||
@RequestParam(defaultValue = "2099-01-01T00:00:00") LocalDateTime to,
|
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime from,
|
||||||
|
@RequestParam(defaultValue = "2099-01-01T00:00:00Z")
|
||||||
|
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime to,
|
||||||
@RequestParam(defaultValue = "false") Boolean includeUncategorized
|
@RequestParam(defaultValue = "false") Boolean includeUncategorized
|
||||||
) {
|
) {
|
||||||
return ok(statisticsService.spendingByCategoryOverTime(categoryId, period, from, to));
|
return ok(statisticsService.spendingByCategoryOverTime(categoryId, period, from, to));
|
||||||
|
@ -61,8 +66,10 @@ public class StatisticsController extends AbstractRestController {
|
||||||
@PreAuthorize("@authService.isOwner(#categoryId, T(dev.mvvasilev.finances.entity.TransactionCategory))")
|
@PreAuthorize("@authService.isOwner(#categoryId, T(dev.mvvasilev.finances.entity.TransactionCategory))")
|
||||||
public ResponseEntity<APIResponseDTO<Double>> sum(
|
public ResponseEntity<APIResponseDTO<Double>> sum(
|
||||||
Long[] categoryId,
|
Long[] categoryId,
|
||||||
@RequestParam(defaultValue = "1970-01-01T00:00:00") LocalDateTime from,
|
@RequestParam(defaultValue = "1970-01-01T00:00:00Z")
|
||||||
@RequestParam(defaultValue = "2099-01-01T00:00:00") LocalDateTime to,
|
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime from,
|
||||||
|
@RequestParam(defaultValue = "2099-01-01T00:00:00Z")
|
||||||
|
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime to,
|
||||||
@RequestParam(defaultValue = "false") Boolean includeUncategorized
|
@RequestParam(defaultValue = "false") Boolean includeUncategorized
|
||||||
) {
|
) {
|
||||||
return ok(statisticsService.sumByCategory(categoryId, from, to, includeUncategorized));
|
return ok(statisticsService.sumByCategory(categoryId, from, to, includeUncategorized));
|
||||||
|
|
|
@ -74,10 +74,14 @@ public class StatisticsRepository {
|
||||||
public Double sumByCategory(Long[] categoryId, LocalDateTime from, LocalDateTime to, Boolean includeUncategorized) {
|
public Double sumByCategory(Long[] categoryId, LocalDateTime from, LocalDateTime to, Boolean includeUncategorized) {
|
||||||
Query nativeQuery = entityManager.createNativeQuery(
|
Query nativeQuery = entityManager.createNativeQuery(
|
||||||
"""
|
"""
|
||||||
SELECT SUM(pt.amount) AS result
|
WITH transactions AS (
|
||||||
|
SELECT DISTINCT pt.*
|
||||||
FROM transactions.processed_transaction AS pt
|
FROM transactions.processed_transaction AS pt
|
||||||
LEFT OUTER JOIN categories.processed_transaction_category AS ptc ON pt.id = ptc.processed_transaction_id
|
LEFT OUTER JOIN categories.processed_transaction_category AS ptc ON pt.id = ptc.processed_transaction_id
|
||||||
WHERE (ptc.category_id = any(?1) OR (?4 AND ptc.category_id IS NULL)) AND (pt.timestamp BETWEEN ?2 AND ?3)
|
WHERE (pt.timestamp BETWEEN ?2 AND ?3) AND (ptc.category_id = any(?1) OR (?4 AND ptc.category_id IS NULL))
|
||||||
|
)
|
||||||
|
SELECT COALESCE(SUM(pt.amount), 0) AS result
|
||||||
|
FROM transactions AS pt
|
||||||
""",
|
""",
|
||||||
Tuple.class
|
Tuple.class
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Grid from "@mui/material/Unstable_Grid2";
|
import Grid from "@mui/material/Unstable_Grid2";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import {Addchart, Save} from "@mui/icons-material";
|
import {Addchart, Save, Warning} from "@mui/icons-material";
|
||||||
import WidgetContainer from "@/components/widgets/WidgetContainer.jsx";
|
import WidgetContainer from "@/components/widgets/WidgetContainer.jsx";
|
||||||
import 'react-grid-layout/css/styles.css';
|
import 'react-grid-layout/css/styles.css';
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
|
@ -25,6 +25,7 @@ export default function StatisticsPage() {
|
||||||
const [isWidgetModalOpen, openWidgetModal] = useState(false);
|
const [isWidgetModalOpen, openWidgetModal] = useState(false);
|
||||||
const [isRemoveWidgetDialogShown, showRemoveWidgetDialog] = useState(false);
|
const [isRemoveWidgetDialogShown, showRemoveWidgetDialog] = useState(false);
|
||||||
const [removingWidgetId, setRemovingWidgetId] = useState(null);
|
const [removingWidgetId, setRemovingWidgetId] = useState(null);
|
||||||
|
const [isUnsavedLayoutWarningShown, showUnsavedLayoutWarning] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchWidgets();
|
fetchWidgets();
|
||||||
|
@ -48,7 +49,10 @@ export default function StatisticsPage() {
|
||||||
parameters: w.parameters
|
parameters: w.parameters
|
||||||
}
|
}
|
||||||
}) ?? []))
|
}) ?? []))
|
||||||
.then(r => utils.hideSpinner());
|
.then(r => {
|
||||||
|
utils.hideSpinner();
|
||||||
|
showUnsavedLayoutWarning(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function openWidgetCreationModal() {
|
function openWidgetCreationModal() {
|
||||||
|
@ -119,8 +123,11 @@ export default function StatisticsPage() {
|
||||||
|
|
||||||
toast.promise(
|
toast.promise(
|
||||||
Promise.resolve(promises)
|
Promise.resolve(promises)
|
||||||
.then(r => fetchWidgets())
|
//.then(r => fetchWidgets())
|
||||||
.then(r => utils.hideSpinner()),
|
.then(r => {
|
||||||
|
utils.hideSpinner();
|
||||||
|
showUnsavedLayoutWarning(false);
|
||||||
|
}),
|
||||||
{
|
{
|
||||||
loading: "Saving...",
|
loading: "Saving...",
|
||||||
success: "Saved",
|
success: "Saved",
|
||||||
|
@ -202,7 +209,7 @@ export default function StatisticsPage() {
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%"
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
variant="contained"
|
variant={isUnsavedLayoutWarningShown ? "contained" : "outlined"}
|
||||||
onClick={saveWidgetsLayout}
|
onClick={saveWidgetsLayout}
|
||||||
startIcon={<Save/>}
|
startIcon={<Save/>}
|
||||||
>
|
>
|
||||||
|
@ -225,6 +232,7 @@ export default function StatisticsPage() {
|
||||||
})
|
})
|
||||||
|
|
||||||
setWidgets([...newWidgets])
|
setWidgets([...newWidgets])
|
||||||
|
console.log("layout change")
|
||||||
}}
|
}}
|
||||||
draggableCancel=".grid-drag-cancel"
|
draggableCancel=".grid-drag-cancel"
|
||||||
cols={{lg: 12, md: 10, sm: 6, xs: 4, xxs: 2}}
|
cols={{lg: 12, md: 10, sm: 6, xs: 4, xxs: 2}}
|
||||||
|
|
|
@ -41,8 +41,8 @@ export default function WidgetContainer({widget, sx, onEdit, onRemove}) {
|
||||||
|
|
||||||
var includeUncategorized = widget.parameters?.find(p => p.name === PARAMS.INCLUDE_UNCATEGORIZED)?.booleanValue ?? false;
|
var includeUncategorized = widget.parameters?.find(p => p.name === PARAMS.INCLUDE_UNCATEGORIZED)?.booleanValue ?? false;
|
||||||
|
|
||||||
queryString += `&fromDate=${fromDate}`;
|
queryString += `&from=${fromDate.toISOString()}`;
|
||||||
queryString += `&toDate=${toDate}`;
|
queryString += `&to=${toDate.toISOString()}`;
|
||||||
queryString += `&includeUncategorized=${includeUncategorized}`;
|
queryString += `&includeUncategorized=${includeUncategorized}`;
|
||||||
|
|
||||||
switch (widget.type) {
|
switch (widget.type) {
|
||||||
|
@ -118,7 +118,7 @@ export default function WidgetContainer({widget, sx, onEdit, onRemove}) {
|
||||||
<Grid xs={12} lg={12}>
|
<Grid xs={12} lg={12}>
|
||||||
<div className={"grid-drag-cancel"} style={{ position: "relative", height: "100%", width: "100%" }}>
|
<div className={"grid-drag-cancel"} style={{ position: "relative", height: "100%", width: "100%" }}>
|
||||||
{
|
{
|
||||||
data && widget.type === "TOTAL_SPENDING_PER_CATEGORY" &&
|
!utils.isNullOrUndefined(data) && widget.type === "TOTAL_SPENDING_PER_CATEGORY" &&
|
||||||
<Pie
|
<Pie
|
||||||
options={{
|
options={{
|
||||||
responsive: true,
|
responsive: true,
|
||||||
|
@ -137,11 +137,11 @@ export default function WidgetContainer({widget, sx, onEdit, onRemove}) {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
data && widget.type === "SPENDING_OVER_TIME_PER_CATEGORY" &&
|
!utils.isNullOrUndefined(data) && widget.type === "SPENDING_OVER_TIME_PER_CATEGORY" &&
|
||||||
<Line />
|
<Line />
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
data && widget.type === "SUM_PER_CATEGORY" &&
|
!utils.isNullOrUndefined(data) && widget.type === "SUM_PER_CATEGORY" &&
|
||||||
<Typography sx={{
|
<Typography sx={{
|
||||||
fontSize: "2.3em"
|
fontSize: "2.3em"
|
||||||
}}>
|
}}>
|
||||||
|
|
|
@ -41,6 +41,9 @@ let utils = {
|
||||||
},
|
},
|
||||||
formatCurrency(number) {
|
formatCurrency(number) {
|
||||||
return LEV_FORMAT.format(number);
|
return LEV_FORMAT.format(number);
|
||||||
|
},
|
||||||
|
isNullOrUndefined(obj) {
|
||||||
|
return obj === null || obj === undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue