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 0000000000000000000000000000000000000000..b21f2f1d9ac9f549510b3fc78a484801c60f7482 --- /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 4f2376475dbe7380e5a76e157115ce6d6d16d4bf..969528dd3104741518b4a369dfa36acb50f905e5 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 5ba28f33aca5652a1c80f05f16daa06168468cb9..7f6d70b4598a7eb194c14e47d408efcb9c342d04 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 6e25a5d41b0c0f23b7a73811f80b45806b1e317a..53f935e1c1c4261cdd5983b621043c58a19f71de 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 f1ef86e5702276e5d95d78c2095b1014b217b473..363e1f1f7ce27421744926dd42ec22c65d586e55 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 4fc5f02c795a6981203656de601a1801e97956b2..e6a2bb93f21971529c2d37100f5a34f8752b9239 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 adcdae5d25eb99b8f5ab36e95d6bde24eca34ea7..5acb5117b4231f1678cefd3246fe74185b2d933e 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 8064b170a314d966b32a594aa71764787e0b14e4..646290397e2f475bdd563212e961541e29c514e9 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 6296f249e2a44ad5e95f0caef53871bd6d74bca1..99fc598e40afafb1d5915f99dc7fbe4aa53f9d65 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 5c470970e7d41c1e82191f4ac55a3b2107369eba..c05c23c73259fcd45c34be346154b2f7f4e0533b 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 0000000000000000000000000000000000000000..5c16b60697a61211e8b79e4fe619cbdf334ac4ee --- /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 8de75323a20cc267ff6e81120cc56ab3aeb3ecde..7a884854b8674f267095f9d1f5a081028cf77e76 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 54c27d783f2560d08e28c017bf025986a4c63cc0..b7450a6da4264b3807cc4650bb60a4b1dc63f6ab 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 36313a491d138c63ed780213b1d46c297f3e97f9..2e511b56a79e5c024c81d95e116bc172aa4cc868 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 9902ec545c69d538e687a76bfb75061a838b02c5..c0a7602c2122a9e263e5815e99b7b80f376a3695 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>