From b537e8ebc4b37fc758ce39e54e3f51b5cab2b048 Mon Sep 17 00:00:00 2001 From: Robert Goldmann <deadlocker@gmx.de> Date: Thu, 8 Dec 2022 22:44:13 +0100 Subject: [PATCH] #683 - fetch matching transaction for chart area on click and display them --- .../budgetmaster/charts/ChartAmountType.java | 6 +++ .../budgetmaster/charts/ChartController.java | 41 +++++++++++++++++++ .../budgetmaster/charts/ChartSettings.java | 30 +++++++++++++- .../budgetmaster/charts/DefaultCharts.java | 2 +- .../IncomesAndExpendituresByCategoryBar.js | 21 ++++++++++ .../resources/languages/base_de.properties | 2 + .../resources/languages/base_en.properties | 2 + .../src/main/resources/static/css/charts.css | 4 ++ .../src/main/resources/static/js/charts.js | 29 +++++++++++++ .../resources/templates/charts/charts.ftl | 6 +++ .../templates/charts/matchingTransactions.ftl | 10 +++++ .../resources/templates/helpers/header.ftl | 4 +- .../resources/templates/search/search.ftl | 34 +-------------- .../templates/search/searchMacros.ftl | 36 ++++++++++++++++ .../transactions/transactionsMacros.ftl | 6 +-- 15 files changed, 192 insertions(+), 41 deletions(-) create mode 100644 BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/ChartAmountType.java create mode 100644 BudgetMasterServer/src/main/resources/templates/charts/matchingTransactions.ftl diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/ChartAmountType.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/ChartAmountType.java new file mode 100644 index 000000000..b21f2f1d9 --- /dev/null +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/ChartAmountType.java @@ -0,0 +1,6 @@ +package de.deadlocker8.budgetmaster.charts; + +public enum ChartAmountType +{ + INCOME, EXPENDITURE +} diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/ChartController.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/ChartController.java index 4f2376475..969528dd3 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/ChartController.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/ChartController.java @@ -7,6 +7,7 @@ import com.google.gson.JsonSerializer; import de.deadlocker8.budgetmaster.controller.BaseController; import de.deadlocker8.budgetmaster.filter.FilterConfiguration; import de.deadlocker8.budgetmaster.filter.FilterHelpersService; +import de.deadlocker8.budgetmaster.filter.FilterObject; import de.deadlocker8.budgetmaster.services.DateService; import de.deadlocker8.budgetmaster.services.HelpersService; import de.deadlocker8.budgetmaster.transactions.Transaction; @@ -52,6 +53,8 @@ public class ChartController extends BaseController public static final String CUSTOM_CHARTS = "customCharts"; public static final String DEFAULT_CHARTS = "defaultCharts"; public static final String DATE_RANGE = "dateRange"; + public static final String MATCHING_TRANSACTIONS = "matchingTransactions"; + public static final String MATCHING_TRANSACTIONS_TITLE = "matchingTransactionsTitle"; } private static class ReturnValues @@ -62,6 +65,7 @@ public class ChartController extends BaseController public static final String NEW_ENTITY = "charts/newChart"; public static final String DELETE_ENTITY = "charts/deleteChartModal"; public static final String ERROR_400 = "error/400"; + public static final String SHOW_MATCHING_TRANSACTIONS = "charts/matchingTransactions"; } private static final Gson GSON = new GsonBuilder() @@ -263,4 +267,41 @@ public class ChartController extends BaseController return ReturnValues.REDIRECT_MANAGE; } + + @PostMapping("/getMatchingTransactions") + public String getMatchingTransactions(Model model, @ModelAttribute("NewChartSettings") ChartSettings chartSettings) + { + String title; + final String clickedCategory = chartSettings.getClickedCategory(); + if(clickedCategory == null) + { + if(chartSettings.getClickedAmountType() == ChartAmountType.INCOME) + { + title = Localization.getString("titles.incomes"); + chartSettings.getFilterConfiguration().setIncludeIncome(true); + chartSettings.getFilterConfiguration().setIncludeExpenditure(false); + } + else + { + title = Localization.getString("title.expenditures"); + chartSettings.getFilterConfiguration().setIncludeIncome(false); + chartSettings.getFilterConfiguration().setIncludeExpenditure(true); + } + } + else + { + for(FilterObject filterCategory : chartSettings.getFilterConfiguration().getFilterCategories()) + { + filterCategory.setInclude(filterCategory.getName().equals(clickedCategory)); + } + + title = Localization.getString("chart.matching.transactions.title.category", clickedCategory); + } + + final List<Transaction> transactions = transactionService.getTransactionsForAccount(helpers.getCurrentAccount(), chartSettings.getStartDate(), chartSettings.getEndDate(), chartSettings.getFilterConfiguration()); + final List<Transaction> convertedTransactions = convertTransferAmounts(transactions); + model.addAttribute(ModelAttributes.MATCHING_TRANSACTIONS, convertedTransactions); + model.addAttribute(ModelAttributes.MATCHING_TRANSACTIONS_TITLE, title); + return ReturnValues.SHOW_MATCHING_TRANSACTIONS; + } } \ No newline at end of file diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/ChartSettings.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/ChartSettings.java index 5ba28f33a..7f6d70b45 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/ChartSettings.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/ChartSettings.java @@ -20,17 +20,19 @@ public class ChartSettings @DateTimeFormat(pattern = "dd.MM.yyyy") private LocalDate endDate; private FilterConfiguration filterConfiguration; + private ChartAmountType clickedAmountType; + private String clickedCategory; public static ChartSettings getDefault(FilterConfiguration filterConfiguration) { - return new ChartSettings(ChartDisplayType.BAR, ChartGroupType.MONTH, null, LocalDate.now().with(firstDayOfMonth()), LocalDate.now().with(lastDayOfMonth()), filterConfiguration); + return new ChartSettings(ChartDisplayType.BAR, ChartGroupType.MONTH, null, LocalDate.now().with(firstDayOfMonth()), LocalDate.now().with(lastDayOfMonth()), filterConfiguration, null, null); } public ChartSettings() { } - public ChartSettings(ChartDisplayType displayType, ChartGroupType groupType, Integer chartID, LocalDate startDate, LocalDate endDate, FilterConfiguration filterConfiguration) + public ChartSettings(ChartDisplayType displayType, ChartGroupType groupType, Integer chartID, LocalDate startDate, LocalDate endDate, FilterConfiguration filterConfiguration, ChartAmountType clickedAmountType, String clickedCategory) { this.displayType = displayType; this.groupType = groupType; @@ -38,6 +40,8 @@ public class ChartSettings this.startDate = startDate; this.endDate = endDate; this.filterConfiguration = filterConfiguration; + this.clickedAmountType = clickedAmountType; + this.clickedCategory = clickedCategory; } public ChartDisplayType getDisplayType() @@ -100,6 +104,26 @@ public class ChartSettings this.filterConfiguration = filterConfiguration; } + public ChartAmountType getClickedAmountType() + { + return clickedAmountType; + } + + public void setClickedAmountType(ChartAmountType clickedAmountType) + { + this.clickedAmountType = clickedAmountType; + } + + public String getClickedCategory() + { + return clickedCategory; + } + + public void setClickedCategory(String clickedCategory) + { + this.clickedCategory = clickedCategory; + } + public boolean isChartSelected() { return chartID != null; @@ -115,6 +139,8 @@ public class ChartSettings ", startDate=" + startDate + ", endDate=" + endDate + ", filterConfiguration=" + filterConfiguration + + ", clickedAmountType=" + clickedAmountType + + ", clickedCategory='" + clickedCategory + '\'' + '}'; } } diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/DefaultCharts.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/DefaultCharts.java index 6e25a5d41..53f935e1c 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/DefaultCharts.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/DefaultCharts.java @@ -32,7 +32,7 @@ public class DefaultCharts private static final Chart CHART_INCOMES_AND_EXPENDITURES_BY_CATEGORY_BAR = new Chart("charts.default.incomesAndExpendituresByCategoryBar", getChartFromFile("charts/IncomesAndExpendituresByCategoryBar.js"), - ChartType.DEFAULT, 9, ChartDisplayType.BAR, ChartGroupType.NONE, "incomesAndExpendituresByCategoryBar.png"); + ChartType.DEFAULT, 30, ChartDisplayType.BAR, ChartGroupType.NONE, "incomesAndExpendituresByCategoryBar.png"); private static final Chart CHART_INCOMES_AND_EXPENDITURES_BY_CATEGORY_PIE = new Chart("charts.default.incomesAndExpendituresByCategoryPie", getChartFromFile("charts/IncomesAndExpendituresByCategoryPie.js"), diff --git a/BudgetMasterServer/src/main/resources/charts/IncomesAndExpendituresByCategoryBar.js b/BudgetMasterServer/src/main/resources/charts/IncomesAndExpendituresByCategoryBar.js index f1ef86e57..363e1f1f7 100644 --- a/BudgetMasterServer/src/main/resources/charts/IncomesAndExpendituresByCategoryBar.js +++ b/BudgetMasterServer/src/main/resources/charts/IncomesAndExpendituresByCategoryBar.js @@ -137,6 +137,27 @@ var plotlyConfig = { Plotly.newPlot("containerID", plotlyData, plotlyLayout, plotlyConfig); +REGEX_CATGEORY_NAME = new RegExp("(.*)\\s\\d+.\\d%"); + +var plotContainer = document.getElementById('containerID'); +plotContainer.on('plotly_click', function(data){ + if(data.event.shiftKey !== true) + { + return; + } + + let index = data.points.length - 1; + let hoverText = data.points[index].hovertext; + let match = hoverText.match(REGEX_CATGEORY_NAME); + if(match === null) + { + console.error('could not extract category name from: "' + hoverText + '"'); + return; + } + + getAndShowMatchingTransactions(null, match[1]); +}); + function prepareHoverText(categoryName, percentage, value) { value = value / 100; diff --git a/BudgetMasterServer/src/main/resources/languages/base_de.properties b/BudgetMasterServer/src/main/resources/languages/base_de.properties index 4fc5f02c7..e6a2bb93f 100644 --- a/BudgetMasterServer/src/main/resources/languages/base_de.properties +++ b/BudgetMasterServer/src/main/resources/languages/base_de.properties @@ -571,6 +571,8 @@ chart.group.type.none=keine chart.group.type.month=Monat chart.group.type.year=Jahr +chart.matching.transactions.title.category=Buchungen in der Kategorie "{0}" + charts.default.accountSumPerDay=Kontostand pro Tag charts.default.accountSumPerDay.localization='{"axisY": "Summe in "'} charts.default.categories=Eingaben/Ausgaben pro Kategorie diff --git a/BudgetMasterServer/src/main/resources/languages/base_en.properties b/BudgetMasterServer/src/main/resources/languages/base_en.properties index adcdae5d2..5acb5117b 100644 --- a/BudgetMasterServer/src/main/resources/languages/base_en.properties +++ b/BudgetMasterServer/src/main/resources/languages/base_en.properties @@ -570,6 +570,8 @@ chart.group.type.none=None chart.group.type.month=Month chart.group.type.year=Year +chart.matching.transactions.title.category=Transactions with category "{0}" + charts.default.accountSumPerDay=Account sum per day charts.default.accountSumPerDay.localization='{"axisY": "Sum in "'} charts.default.categories=Incomes/Expenditures per category diff --git a/BudgetMasterServer/src/main/resources/static/css/charts.css b/BudgetMasterServer/src/main/resources/static/css/charts.css index 8064b170a..646290397 100644 --- a/BudgetMasterServer/src/main/resources/static/css/charts.css +++ b/BudgetMasterServer/src/main/resources/static/css/charts.css @@ -101,4 +101,8 @@ .charts-default-margin { margin-top: 2rem; +} + +#matchingTransactionsTitle { + margin-top: 2rem; } \ No newline at end of file diff --git a/BudgetMasterServer/src/main/resources/static/js/charts.js b/BudgetMasterServer/src/main/resources/static/js/charts.js index 6296f249e..99fc598e4 100644 --- a/BudgetMasterServer/src/main/resources/static/js/charts.js +++ b/BudgetMasterServer/src/main/resources/static/js/charts.js @@ -366,3 +366,32 @@ function formatChartTitle(title, subtitle) subtitle = '<span style="font-size: 0.9rem;">' + subtitle + '</span>'; return title + '<br>' + subtitle; } + +function getAndShowMatchingTransactions(clickedAmountType, clickedCategory) +{ + document.getElementsByName('clickedAmountType')[0].value = clickedAmountType; + document.getElementsByName('clickedCategory')[0].value = clickedCategory; + + let form = document.getElementsByName('NewChartSettings')[0]; + + $.ajax({ + type: 'POST', + url: rootURL + '/charts/getMatchingTransactions', + data: new FormData(form), + processData: false, + contentType: false, + success: function(response) + { + let overviewElement = document.getElementById('matchingTransactionsOverview'); + overviewElement.innerHTML = response; + }, + error: function(response) + { + M.toast({ + html: "Error fetching matching transactions", + classes: 'red' + }); + console.error(response); + } + }); +} diff --git a/BudgetMasterServer/src/main/resources/templates/charts/charts.ftl b/BudgetMasterServer/src/main/resources/templates/charts/charts.ftl index 5c470970e..c05c23c73 100644 --- a/BudgetMasterServer/src/main/resources/templates/charts/charts.ftl +++ b/BudgetMasterServer/src/main/resources/templates/charts/charts.ftl @@ -5,6 +5,7 @@ <@header.header "BudgetMaster - ${locale.getString('menu.charts')}"/> <@header.style "datepicker"/> <@header.style "collapsible"/> + <@header.style "search"/> <@header.style "charts"/> <#import "/spring.ftl" as s> </head> @@ -72,6 +73,9 @@ <@filterOptions/> + <input type="hidden" name="clickedAmountType" value=""> + <input type="hidden" name="clickedCategory" value=""> + <#-- buttons --> <div class="row center-align"> <div class="col s12"> @@ -89,6 +93,8 @@ <div id="${containerID}" class="chart-canvas"></div> </#if> </div> + + <div id="matchingTransactionsOverview"></div> </@header.content> </div> </main> diff --git a/BudgetMasterServer/src/main/resources/templates/charts/matchingTransactions.ftl b/BudgetMasterServer/src/main/resources/templates/charts/matchingTransactions.ftl new file mode 100644 index 000000000..5c16b6069 --- /dev/null +++ b/BudgetMasterServer/src/main/resources/templates/charts/matchingTransactions.ftl @@ -0,0 +1,10 @@ +<#import "/spring.ftl" as s> +<#import "../helpers/header.ftl" as header> +<#import "../search/searchMacros.ftl" as searchMacros> + +<@header.globals/> + +<div id="matchingTransactionsTitle" class="section center-align"> + <div class="headline-small">${matchingTransactionsTitle}</div> +</div> +<@searchMacros.renderTransactions transactions=matchingTransactions openLinksInNewTab=true/> diff --git a/BudgetMasterServer/src/main/resources/templates/helpers/header.ftl b/BudgetMasterServer/src/main/resources/templates/helpers/header.ftl index 8de75323a..7a884854b 100644 --- a/BudgetMasterServer/src/main/resources/templates/helpers/header.ftl +++ b/BudgetMasterServer/src/main/resources/templates/helpers/header.ftl @@ -136,8 +136,8 @@ </button> </#macro> -<#macro buttonFlat url icon localizationKey id="" classes="" isDataUrl=false noUrl=false iconClasses=''> - <a <#if !isDataUrl && !noUrl>href="<@s.url url/>"</#if> +<#macro buttonFlat url icon localizationKey id="" classes="" isDataUrl=false noUrl=false iconClasses='' target=''> + <a <#if target?has_content>target="${target}"</#if> <#if !isDataUrl && !noUrl>href="<@s.url url/>"</#if> id="${id}" class="waves-effect waves-light btn-flat ${classes}" <#if isDataUrl>data-url="${url}"</#if>> diff --git a/BudgetMasterServer/src/main/resources/templates/search/search.ftl b/BudgetMasterServer/src/main/resources/templates/search/search.ftl index 54c27d783..b7450a6da 100644 --- a/BudgetMasterServer/src/main/resources/templates/search/search.ftl +++ b/BudgetMasterServer/src/main/resources/templates/search/search.ftl @@ -12,7 +12,6 @@ <#import "../helpers/navbar.ftl" as navbar> <@navbar.navbar "home" settings/> - <#import "../transactions/transactionsMacros.ftl" as transactionsMacros> <#import "searchMacros.ftl" as searchMacros> <main> @@ -35,38 +34,7 @@ <div class="row search-container"> <div class="col s12"> - <#list page.getContent() as transaction> - <div class="card-panel search-result"> - <div class="hide-on-large-only"> - <div class="row valign-wrapper"> - <div class="col s3 center-align bold transaction-text"> - ${dateService.getDateStringNormal(transaction.date)} - </div> - <@transactionsMacros.transactionAccountIcon transaction/> - <@transactionsMacros.transactionType transaction "s2"/> - <@transactionsMacros.transactionLinks transaction/> - </div> - <div class="row valign-wrapper no-margin-bottom"> - <@transactionsMacros.transactionCategory transaction "center-align"/> - <@transactionsMacros.transactionNameAndDescription transaction "s5"/> - <@transactionsMacros.transactionAmount transaction transaction.getAccount() "s4"/> - </div> - </div> - <div class="hide-on-med-and-down"> - <div class="row valign-wrapper no-margin-bottom transaction-row-desktop"> - <div class="col l2 xl1 bold transaction-text transaction-date valign-wrapper"> - ${dateService.getDateStringNormal(transaction.date)} - </div> - <@transactionsMacros.transactionCategory transaction "left-align"/> - <@transactionsMacros.transactionAccountIcon transaction/> - <@transactionsMacros.transactionType transaction "l1 xl1"/> - <@transactionsMacros.transactionNameAndDescription transaction "l3 xl4"/> - <@transactionsMacros.transactionAmount transaction transaction.getAccount() "l2 xl2"/> - <@transactionsMacros.transactionLinks transaction/> - </div> - </div> - </div> - </#list> + <@searchMacros.renderTransactions page.getContent()/> <#-- placeholder --> <#if page.getContent()?size == 0> diff --git a/BudgetMasterServer/src/main/resources/templates/search/searchMacros.ftl b/BudgetMasterServer/src/main/resources/templates/search/searchMacros.ftl index 36313a491..2e511b56a 100644 --- a/BudgetMasterServer/src/main/resources/templates/search/searchMacros.ftl +++ b/BudgetMasterServer/src/main/resources/templates/search/searchMacros.ftl @@ -1,5 +1,6 @@ <#import "/spring.ftl" as s> <#import "../helpers/header.ftl" as header> +<#import "../transactions/transactionsMacros.ftl" as transactionsMacros> <#macro searchTextAndButton search> <div class="row no-margin-bottom valign-wrapper"> @@ -122,4 +123,39 @@ </#if> </div> </div> +</#macro> + +<#macro renderTransactions transactions openLinksInNewTab=false> + <#list transactions as transaction> + <div class="card-panel search-result"> + <div class="hide-on-large-only"> + <div class="row valign-wrapper"> + <div class="col s3 center-align bold transaction-text"> + ${dateService.getDateStringNormal(transaction.date)} + </div> + <@transactionsMacros.transactionAccountIcon transaction/> + <@transactionsMacros.transactionType transaction "s2"/> + <@transactionsMacros.transactionLinks transaction=transaction target='_blank'/> + </div> + <div class="row valign-wrapper no-margin-bottom"> + <@transactionsMacros.transactionCategory transaction "center-align"/> + <@transactionsMacros.transactionNameAndDescription transaction "s5"/> + <@transactionsMacros.transactionAmount transaction transaction.getAccount() "s4"/> + </div> + </div> + <div class="hide-on-med-and-down"> + <div class="row valign-wrapper no-margin-bottom transaction-row-desktop"> + <div class="col l2 xl1 bold transaction-text transaction-date valign-wrapper"> + ${dateService.getDateStringNormal(transaction.date)} + </div> + <@transactionsMacros.transactionCategory transaction "left-align"/> + <@transactionsMacros.transactionAccountIcon transaction/> + <@transactionsMacros.transactionType transaction "l1 xl1"/> + <@transactionsMacros.transactionNameAndDescription transaction "l3 xl4"/> + <@transactionsMacros.transactionAmount transaction transaction.getAccount() "l2 xl2"/> + <@transactionsMacros.transactionLinks transaction=transaction target='_blank'/> + </div> + </div> + </div> + </#list> </#macro> \ No newline at end of file diff --git a/BudgetMasterServer/src/main/resources/templates/transactions/transactionsMacros.ftl b/BudgetMasterServer/src/main/resources/templates/transactions/transactionsMacros.ftl index 9902ec545..c0a7602c2 100644 --- a/BudgetMasterServer/src/main/resources/templates/transactions/transactionsMacros.ftl +++ b/BudgetMasterServer/src/main/resources/templates/transactions/transactionsMacros.ftl @@ -98,11 +98,11 @@ </#if> </#macro> -<#macro transactionLinks transaction> +<#macro transactionLinks transaction target=''> <div class="col s4 l2 xl1 right-align transaction-buttons no-wrap"> - <@header.buttonFlat url='/transactions/' + transaction.ID?c + '/highlight' icon='open_in_new' localizationKey='' classes="no-padding text-default buttonHighlight"/> + <@header.buttonFlat url='/transactions/' + transaction.ID?c + '/highlight' icon='open_in_new' localizationKey='' classes="no-padding text-default buttonHighlight" target=target/> <#if transaction.getAccount().getAccountState().name() == 'FULL_ACCESS'> - <@header.buttonFlat url='/transactions/' + transaction.ID?c + '/edit' icon='edit' localizationKey='' classes="no-padding text-default"/> + <@header.buttonFlat url='/transactions/' + transaction.ID?c + '/edit' icon='edit' localizationKey='' classes="no-padding text-default" target=target/> </#if> </div> </#macro> -- GitLab