From e71c59117b604863706bd7e133496c7e8732c7ee Mon Sep 17 00:00:00 2001 From: Robert Goldmann <deadlocker@gmx.de> Date: Sun, 14 Apr 2019 12:44:16 +0200 Subject: [PATCH] #415 - offer to move transactions to category on category delete --- .../categories/CategoryController.java | 17 +++-- .../categories/CategoryService.java | 4 +- .../categories/DestinationCategory.java | 28 ++++++++ .../transactions/TransactionController.java | 2 + src/main/resources/languages/_de.properties | 2 + src/main/resources/languages/_en.properties | 2 + src/main/resources/static/js/categories.js | 5 ++ .../resources/static/js/categorySelect.js | 70 +++++++++++++++++++ src/main/resources/static/js/transactions.js | 69 ------------------ .../templates/categories/categories.ftl | 19 ++++- .../templates/transactions/newTransaction.ftl | 3 +- .../transactions/newTransactionMacros.ftl | 11 ++- 12 files changed, 149 insertions(+), 83 deletions(-) create mode 100644 src/main/java/de/deadlocker8/budgetmaster/categories/DestinationCategory.java create mode 100644 src/main/resources/static/js/categorySelect.js diff --git a/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryController.java b/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryController.java index 9a729e4f0..72059d64a 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryController.java @@ -15,6 +15,9 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import java.util.List; +import java.util.stream.Collectors; + @Controller public class CategoryController extends BaseController @@ -47,18 +50,24 @@ public class CategoryController extends BaseController return "redirect:/categories"; } - model.addAttribute("categories", categoryRepository.findAllByOrderByNameAsc()); + List<Category> allCategories = categoryService.getRepository().findAllByOrderByNameAsc(); + List<Category> availableCategories = allCategories.stream().filter(category -> !category.getID().equals(ID)).collect(Collectors.toList()); + + model.addAttribute("categories", allCategories); + model.addAttribute("availableCategories", availableCategories); + model.addAttribute("preselectedCategory", categoryService.getRepository().findByType(CategoryType.NONE)); + model.addAttribute("currentCategory", categoryRepository.getOne(ID)); model.addAttribute("settings", settingsService.getSettings()); return "categories/categories"; } - @RequestMapping("/categories/{ID}/delete") - public String deleteCategory(Model model, @PathVariable("ID") Integer ID) + @RequestMapping(value = "/categories/{ID}/delete", method = RequestMethod.POST) + public String deleteCategory(Model model, @PathVariable("ID") Integer ID, @ModelAttribute("DestinationCategory") DestinationCategory destinationCategory) { if(isDeletable(ID)) { - categoryService.deleteCategory(ID); + categoryService.deleteCategory(ID, destinationCategory.getCategory()); } return "redirect:/categories"; diff --git a/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryService.java b/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryService.java index cabe084ff..ca9f4223e 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryService.java +++ b/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryService.java @@ -33,7 +33,7 @@ public class CategoryService implements Resetable return categoryRepository; } - public void deleteCategory(int ID) + public void deleteCategory(int ID, Category newCategory) { Category categoryToDelete = categoryRepository.findOne(ID); List<Transaction> referringTransactions = categoryToDelete.getReferringTransactions(); @@ -41,7 +41,7 @@ public class CategoryService implements Resetable { for(Transaction transaction : referringTransactions) { - transaction.setCategory(categoryRepository.findByType(CategoryType.NONE)); + transaction.setCategory(newCategory); } } diff --git a/src/main/java/de/deadlocker8/budgetmaster/categories/DestinationCategory.java b/src/main/java/de/deadlocker8/budgetmaster/categories/DestinationCategory.java new file mode 100644 index 000000000..582fd0696 --- /dev/null +++ b/src/main/java/de/deadlocker8/budgetmaster/categories/DestinationCategory.java @@ -0,0 +1,28 @@ +package de.deadlocker8.budgetmaster.categories; + +public class DestinationCategory +{ + private Category category; + + public DestinationCategory() + { + } + + public Category getCategory() + { + return category; + } + + public void setCategory(Category category) + { + this.category = category; + } + + @Override + public String toString() + { + return "DestinationCategory{" + + "category=" + category + + '}'; + } +} diff --git a/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionController.java b/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionController.java index c3e762a7b..b30248f46 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionController.java @@ -1,6 +1,7 @@ package de.deadlocker8.budgetmaster.transactions; import de.deadlocker8.budgetmaster.categories.CategoryRepository; +import de.deadlocker8.budgetmaster.categories.CategoryType; import de.deadlocker8.budgetmaster.controller.BaseController; import de.deadlocker8.budgetmaster.repeating.RepeatingOptionRepository; import de.deadlocker8.budgetmaster.settings.Settings; @@ -128,6 +129,7 @@ public class TransactionController extends BaseController { DateTime date = helpers.getDateTimeFromCookie(cookieDate); Transaction emptyTransaction = new Transaction(); + emptyTransaction.setCategory(categoryRepository.findByType(CategoryType.NONE)); model.addAttribute("currentDate", date); model.addAttribute("categories", categoryRepository.findAllByOrderByNameAsc()); model.addAttribute("accounts", accountService.getAllAccountsAsc()); diff --git a/src/main/resources/languages/_de.properties b/src/main/resources/languages/_de.properties index 8346807ac..6ceb31a1b 100644 --- a/src/main/resources/languages/_de.properties +++ b/src/main/resources/languages/_de.properties @@ -78,6 +78,8 @@ month.december=Dezember # INFO info.title.category.delete=Kategorie l�schen info.text.category.delete=M�chtest du die Kategorie "{0}" wirklich unwiderruflich l�schen? +info.title.category.delete.move=Verschieben nach +info.text.category.delete.move=In welche Kategorie sollen alle vekn�pften Buchungen verschoben werden? info.title.account.delete=Konto l�schen info.text.account.delete=M�chtest du das Konto "{0}" wirklich unwiderruflich l�schen?<br>Hinweis: Diesem Konto sind {1} Buchungen zugeordnet. Beim L�schen des Kontos werden diese ebenfalls gel�scht! info.button.account.delete=Konto und Buchungen l�schen diff --git a/src/main/resources/languages/_en.properties b/src/main/resources/languages/_en.properties index bcf1596bd..50aa2fcfc 100644 --- a/src/main/resources/languages/_en.properties +++ b/src/main/resources/languages/_en.properties @@ -78,6 +78,8 @@ month.december=December # INFO info.title.category.delete=Delete Category info.text.category.delete=Do you really want to delete the category "{0}"? This can''t be undone. +info.title.category.delete.move=Move to +info.text.category.delete.move=In which category should all related transactions be moved? info.title.account.delete=Delete Account info.text.account.delete=Do you really want to delete the account "{0}"? This can''t be undone.<br>Note: There are {1} transactions associated with this account. Deleting this account will delete all releated transactions too! info.button.account.delete=Delete Account and Transactions diff --git a/src/main/resources/static/js/categories.js b/src/main/resources/static/js/categories.js index 1d4e14a3e..574a1ac29 100644 --- a/src/main/resources/static/js/categories.js +++ b/src/main/resources/static/js/categories.js @@ -26,6 +26,11 @@ $( document ).ready(function() { } }); } + + $('#buttonDeleteCategory').click(function() + { + document.getElementById("formDestinationCategory").submit(); + }); }); function removeActive() diff --git a/src/main/resources/static/js/categorySelect.js b/src/main/resources/static/js/categorySelect.js new file mode 100644 index 000000000..717bd0af5 --- /dev/null +++ b/src/main/resources/static/js/categorySelect.js @@ -0,0 +1,70 @@ +$( document ).ready(function() { + if($("#transaction-category").length) + { + beautifyCategorySelect(); + } +}); + +function beautifyCategorySelect() { + var counter = 0; + + var select = M.FormSelect.init(document.getElementById('transaction-category'), { + dropdownOptions: { + onCloseStart: function () { + var listItems = select.dropdownOptions.childNodes; + var selectedItem; + for(var i = 0; i < listItems.length; i++) + { + var currentItem = listItems[i]; + if(currentItem.classList.contains("selected")) + { + selectedItem = currentItem.textContent; + break; + } + } + select.input.value = selectedItem; + } + } + }); + + select.dropdownOptions.childNodes.forEach(function (item) { + var currentSpan = jQuery(item.querySelector('span')); + var categoryInfo = currentSpan.text().split("@@@"); + var categoryName = categoryInfo[0]; + var firstLetter = capitalizeFirstLetter(categoryName); + var categoryColor = categoryInfo[1]; + var appropriateTextColor = categoryInfo[2]; + + currentSpan.text(categoryName); + currentSpan.data("infos", categoryInfo); + currentSpan.addClass("category-select"); + currentSpan.parent().prepend('<div class="category-circle-small category-select" id="category-' + counter + '" style="background-color: ' + categoryColor + '"><span></span></div>'); + $('#categoryWrapper').parent().append('<style>#category-' + counter + ':after{content: "' + firstLetter + '"; color: ' + appropriateTextColor + ';}</style>'); + + currentSpan.click(function () { + select.input.value = categoryName; + }); + counter++; + }); + + // select current category from code again in order to avoid showing the full infos text (e.g. Test@@@#FFFFFF@#000000@@@1) in the input field by materialize + if(typeof selectedCategory !== 'undefined') + { + var listItems = select.dropdownOptions.childNodes; + for(var i = 0; i < listItems.length; i++) + { + var currentSpan = jQuery(listItems[i].querySelector('span.category-select')); + var categoryID = currentSpan.data("infos")[3]; + if(categoryID === selectedCategory) + { + currentSpan.trigger("click"); + break; + } + } + } +} + +function capitalizeFirstLetter(text) +{ + return text.charAt(0).toUpperCase(); +} \ No newline at end of file diff --git a/src/main/resources/static/js/transactions.js b/src/main/resources/static/js/transactions.js index 4b7b89f09..f3325d354 100644 --- a/src/main/resources/static/js/transactions.js +++ b/src/main/resources/static/js/transactions.js @@ -182,11 +182,6 @@ $( document ).ready(function() { document.getElementById("input-isPayment").value = 1; }); - - if($("#transaction-category").length) - { - beautifyCategorySelect(); - } }); var transactionRepeatingModifierID = "#transaction-repeating-modifier"; @@ -322,67 +317,3 @@ function validateForm() return true; } - -function beautifyCategorySelect() { - var counter = 0; - - var select = M.FormSelect.init(document.getElementById('transaction-category'), { - dropdownOptions: { - onCloseStart: function () { - var listItems = select.dropdownOptions.childNodes; - var selectedItem; - for(var i = 0; i < listItems.length; i++) - { - var currentItem = listItems[i]; - if(currentItem.classList.contains("selected")) - { - selectedItem = currentItem.textContent; - break; - } - } - select.input.value = selectedItem; - } - } - }); - - select.dropdownOptions.childNodes.forEach(function (item) { - var currentSpan = jQuery(item.querySelector('span')); - var categoryInfo = currentSpan.text().split("@@@"); - var categoryName = categoryInfo[0]; - var firstLetter = capitalizeFirstLetter(categoryName); - var categoryColor = categoryInfo[1]; - var appropriateTextColor = categoryInfo[2]; - - currentSpan.text(categoryName); - currentSpan.data("infos", categoryInfo); - currentSpan.addClass("category-select"); - currentSpan.parent().prepend('<div class="category-circle-small category-select" id="category-' + counter + '" style="background-color: ' + categoryColor + '"><span></span></div>'); - $('#categoryWrapper').parent().append('<style>#category-' + counter + ':after{content: "' + firstLetter + '"; color: ' + appropriateTextColor + ';}</style>'); - - currentSpan.click(function () { - select.input.value = categoryName; - }); - counter++; - }); - - // select current category from code again in order to avoid showing the full infos text (e.g. Test@@@#FFFFFF@#000000@@@1) in the input field by materialize - if(typeof selectedCategory !== 'undefined') - { - var listItems = select.dropdownOptions.childNodes; - for(var i = 0; i < listItems.length; i++) - { - var currentSpan = jQuery(listItems[i].querySelector('span.category-select')); - var categoryID = currentSpan.data("infos")[3]; - if(categoryID === selectedCategory) - { - currentSpan.trigger("click"); - break; - } - } - } -} - -function capitalizeFirstLetter(text) -{ - return text.charAt(0).toUpperCase(); -} \ No newline at end of file diff --git a/src/main/resources/templates/categories/categories.ftl b/src/main/resources/templates/categories/categories.ftl index 378774272..9d9c04795 100644 --- a/src/main/resources/templates/categories/categories.ftl +++ b/src/main/resources/templates/categories/categories.ftl @@ -10,6 +10,7 @@ <@navbar.navbar "categories" settings/> <#import "categoriesFunctions.ftl" as categoriesFunctions> + <#import "../transactions/newTransactionMacros.ftl" as newTransactionMacros> <main> <div class="card main-card background-color"> @@ -55,18 +56,34 @@ <div class="modal-content"> <h4>${locale.getString("info.title.category.delete")}</h4> <p>${locale.getString("info.text.category.delete", currentCategory.name)}</p> + <p>${locale.getString("info.text.category.delete.move")}</p> + + <form name="DestinationCategory" id="formDestinationCategory" action="<@s.url '/categories/${currentCategory.ID?c}/delete'/>" method="post"> + <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> + <#import "../helpers/validation.ftl" as validation> + <@newTransactionMacros.categorySelect availableCategories preselectedCategory "col s12 m12 l8 offset-l2" locale.getString("info.title.category.delete.move")/> + </form> </div> + <div class="modal-footer background-color"> <a href="<@s.url '/categories'/>" class="modal-action modal-close waves-effect waves-light red btn-flat white-text">${locale.getString("cancel")}</a> - <a href="<@s.url '/categories/${currentCategory.ID?c}/delete'/>" class="modal-action modal-close waves-effect waves-light green btn-flat white-text">${locale.getString("delete")}</a> + <a id="buttonDeleteCategory" class="modal-action modal-close waves-effect waves-light green btn-flat white-text">${locale.getString("delete")}</a> </div> </div> </#if> </main> <!-- Scripts--> + <#-- pass selected account to JS in order to select current value for materialize select --> + <script> + <#if preselectedCategory??> + selectedCategory = "${preselectedCategory.getID()?c}"; + </#if> + </script> + <#import "../helpers/scripts.ftl" as scripts> <@scripts.scripts/> <script src="<@s.url '/js/categories.js'/>"></script> + <script src="<@s.url '/js/categorySelect.js'/>"></script> </body> </html> \ No newline at end of file diff --git a/src/main/resources/templates/transactions/newTransaction.ftl b/src/main/resources/templates/transactions/newTransaction.ftl index 17cc4e2e1..8530925a3 100644 --- a/src/main/resources/templates/transactions/newTransaction.ftl +++ b/src/main/resources/templates/transactions/newTransaction.ftl @@ -48,7 +48,7 @@ </div> <#-- category --> - <@newTransactionMacros.category categories transaction/> + <@newTransactionMacros.categorySelect categories transaction.getCategory() "col s12 m12 l8 offset-l2" locale.getString("transaction.new.label.category")/> <#-- date --> <div class="row"> @@ -159,5 +159,6 @@ <@scripts.scripts/> <script src="<@s.url '/js/spectrum.js'/>"></script> <script src="<@s.url '/js/transactions.js'/>"></script> + <script src="<@s.url '/js/categorySelect.js'/>"></script> </body> </html> diff --git a/src/main/resources/templates/transactions/newTransactionMacros.ftl b/src/main/resources/templates/transactions/newTransactionMacros.ftl index d4c668a01..8a11ebf6b 100644 --- a/src/main/resources/templates/transactions/newTransactionMacros.ftl +++ b/src/main/resources/templates/transactions/newTransactionMacros.ftl @@ -49,11 +49,10 @@ <a class="waves-effect waves-light btn ${color} buttonExpenditure"><i class="material-icons left">file_upload</i>${locale.getString("title.expenditure")}</a> </#macro> - <#import "../categories/categoriesFunctions.ftl" as categoriesFunctions> -<#macro category categories transaction> +<#macro categorySelect categories selectedCategory inputClasses labelText> <div class="row"> - <div class="input-field col s12 m12 l8 offset-l2" id="categoryWrapper"> + <div class="input-field ${inputClasses}" id="categoryWrapper"> <select id="transaction-category" name="category" <@validation.validation "category"/>> <#list categories as category> <#assign categoryInfos=categoriesFunctions.getCategoryName(category) + "@@@" + category.getColor() + "@@@" + category.getAppropriateTextColor() + "@@@" + category.getID()?c> @@ -62,8 +61,8 @@ <#continue> </#if> - <#if transaction.getCategory()??> - <#if transaction.getCategory().getID()?c == category.getID()?c> + <#if selectedCategory??> + <#if selectedCategory.getID()?c == category.getID()?c> <option selected value="${category.getID()?c}">${categoryInfos}</option> <#else> <option value="${category.getID()?c}">${categoryInfos}</option> @@ -79,7 +78,7 @@ <option value="${category.getID()?c}">${categoryInfos}</option> </#list> </select> - <label for="transaction-category">${locale.getString("transaction.new.label.category")}</label> + <label for="transaction-category">${labelText}</label> </div> </div> </#macro> -- GitLab