From a95fe9dcf4719bc92a0d4a5add5d787450501481 Mon Sep 17 00:00:00 2001 From: mvvasilev Date: Wed, 3 Jan 2024 23:51:12 +0200 Subject: [PATCH] Fix to and from dates for statistics --- .../controllers/StatisticsController.java | 19 +++++++++++++------ .../persistence/StatisticsRepository.java | 12 ++++++++---- frontend/src/app/pages/StatisticsPage.jsx | 18 +++++++++++++----- .../components/widgets/WidgetContainer.jsx | 10 +++++----- frontend/src/utils.js | 3 +++ 5 files changed, 42 insertions(+), 20 deletions(-) diff --git a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/controllers/StatisticsController.java b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/controllers/StatisticsController.java index c033206..74ad098 100644 --- a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/controllers/StatisticsController.java +++ b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/controllers/StatisticsController.java @@ -7,6 +7,7 @@ import dev.mvvasilev.finances.dtos.SpendingOverTimeByCategoryDTO; import dev.mvvasilev.finances.enums.TimePeriod; import dev.mvvasilev.finances.services.StatisticsService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; 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))") public ResponseEntity> fetchSpendingByCategory( Long[] categoryId, - @RequestParam(defaultValue = "1970-01-01T00:00:00") LocalDateTime from, - @RequestParam(defaultValue = "2099-01-01T00:00:00") LocalDateTime to, + @RequestParam(defaultValue = "1970-01-01T00:00:00Z") + @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 ) { return ok(statisticsService.spendingByCategory(categoryId, from, to)); @@ -50,8 +53,10 @@ public class StatisticsController extends AbstractRestController { public ResponseEntity> fetchSpendingOverTimeByCategory( Long[] categoryId, @RequestParam(defaultValue = "DAILY") TimePeriod period, - @RequestParam(defaultValue = "1970-01-01T00:00:00") LocalDateTime from, - @RequestParam(defaultValue = "2099-01-01T00:00:00") LocalDateTime to, + @RequestParam(defaultValue = "1970-01-01T00:00:00Z") + @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 ) { 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))") public ResponseEntity> sum( Long[] categoryId, - @RequestParam(defaultValue = "1970-01-01T00:00:00") LocalDateTime from, - @RequestParam(defaultValue = "2099-01-01T00:00:00") LocalDateTime to, + @RequestParam(defaultValue = "1970-01-01T00:00:00Z") + @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 ) { return ok(statisticsService.sumByCategory(categoryId, from, to, includeUncategorized)); diff --git a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/persistence/StatisticsRepository.java b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/persistence/StatisticsRepository.java index d5f099f..85ed50e 100644 --- a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/persistence/StatisticsRepository.java +++ b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/persistence/StatisticsRepository.java @@ -74,10 +74,14 @@ public class StatisticsRepository { public Double sumByCategory(Long[] categoryId, LocalDateTime from, LocalDateTime to, Boolean includeUncategorized) { Query nativeQuery = entityManager.createNativeQuery( """ - SELECT SUM(pt.amount) AS result - FROM transactions.processed_transaction AS pt - 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) + WITH transactions AS ( + SELECT DISTINCT pt.* + FROM transactions.processed_transaction AS pt + LEFT OUTER JOIN categories.processed_transaction_category AS ptc ON pt.id = ptc.processed_transaction_id + 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 ); diff --git a/frontend/src/app/pages/StatisticsPage.jsx b/frontend/src/app/pages/StatisticsPage.jsx index 329a643..6cb617b 100644 --- a/frontend/src/app/pages/StatisticsPage.jsx +++ b/frontend/src/app/pages/StatisticsPage.jsx @@ -6,7 +6,7 @@ import { } from '@mui/material'; import Grid from "@mui/material/Unstable_Grid2"; 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 'react-grid-layout/css/styles.css'; import {useEffect, useState} from "react"; @@ -25,6 +25,7 @@ export default function StatisticsPage() { const [isWidgetModalOpen, openWidgetModal] = useState(false); const [isRemoveWidgetDialogShown, showRemoveWidgetDialog] = useState(false); const [removingWidgetId, setRemovingWidgetId] = useState(null); + const [isUnsavedLayoutWarningShown, showUnsavedLayoutWarning] = useState(false); useEffect(() => { fetchWidgets(); @@ -48,7 +49,10 @@ export default function StatisticsPage() { parameters: w.parameters } }) ?? [])) - .then(r => utils.hideSpinner()); + .then(r => { + utils.hideSpinner(); + showUnsavedLayoutWarning(false); + }); } function openWidgetCreationModal() { @@ -119,8 +123,11 @@ export default function StatisticsPage() { toast.promise( Promise.resolve(promises) - .then(r => fetchWidgets()) - .then(r => utils.hideSpinner()), + //.then(r => fetchWidgets()) + .then(r => { + utils.hideSpinner(); + showUnsavedLayoutWarning(false); + }), { loading: "Saving...", success: "Saved", @@ -202,7 +209,7 @@ export default function StatisticsPage() { sx={{ width: "100%" }} - variant="contained" + variant={isUnsavedLayoutWarningShown ? "contained" : "outlined"} onClick={saveWidgetsLayout} startIcon={} > @@ -225,6 +232,7 @@ export default function StatisticsPage() { }) setWidgets([...newWidgets]) + console.log("layout change") }} draggableCancel=".grid-drag-cancel" cols={{lg: 12, md: 10, sm: 6, xs: 4, xxs: 2}} diff --git a/frontend/src/components/widgets/WidgetContainer.jsx b/frontend/src/components/widgets/WidgetContainer.jsx index 5a4712a..ff91b52 100644 --- a/frontend/src/components/widgets/WidgetContainer.jsx +++ b/frontend/src/components/widgets/WidgetContainer.jsx @@ -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; - queryString += `&fromDate=${fromDate}`; - queryString += `&toDate=${toDate}`; + queryString += `&from=${fromDate.toISOString()}`; + queryString += `&to=${toDate.toISOString()}`; queryString += `&includeUncategorized=${includeUncategorized}`; switch (widget.type) { @@ -118,7 +118,7 @@ export default function WidgetContainer({widget, sx, onEdit, onRemove}) {
{ - data && widget.type === "TOTAL_SPENDING_PER_CATEGORY" && + !utils.isNullOrUndefined(data) && widget.type === "TOTAL_SPENDING_PER_CATEGORY" && } { - data && widget.type === "SPENDING_OVER_TIME_PER_CATEGORY" && + !utils.isNullOrUndefined(data) && widget.type === "SPENDING_OVER_TIME_PER_CATEGORY" && } { - data && widget.type === "SUM_PER_CATEGORY" && + !utils.isNullOrUndefined(data) && widget.type === "SUM_PER_CATEGORY" && diff --git a/frontend/src/utils.js b/frontend/src/utils.js index 6e5db4a..e7e9b33 100644 --- a/frontend/src/utils.js +++ b/frontend/src/utils.js @@ -41,6 +41,9 @@ let utils = { }, formatCurrency(number) { return LEV_FORMAT.format(number); + }, + isNullOrUndefined(obj) { + return obj === null || obj === undefined; } }