From 239d1612dd102659ed28e12e5cc02548ad058713 Mon Sep 17 00:00:00 2001
From: Robert Goldmann <deadlocker@gmx.de>
Date: Sun, 23 Apr 2023 18:25:00 +0200
Subject: [PATCH] #742 - avoid page reload on skip and undo skip

---
 .../TransactionImportController.java          | 41 +++++++++++----
 .../resources/static/js/transactionImport.js  | 52 ++++++++++++++++++-
 .../resources/templates/helpers/header.ftl    |  3 +-
 .../transactions/transactionImportMacros.ftl  |  7 +--
 .../transactions/transactionImportRow.ftl     |  3 ++
 5 files changed, 92 insertions(+), 14 deletions(-)
 create mode 100644 BudgetMasterServer/src/main/resources/templates/transactions/transactionImportRow.ftl

diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportController.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportController.java
index 2644ca87e..de5e23439 100644
--- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportController.java
+++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportController.java
@@ -5,6 +5,7 @@ import de.deadlocker8.budgetmaster.categories.CategoryService;
 import de.deadlocker8.budgetmaster.controller.BaseController;
 import de.deadlocker8.budgetmaster.transactions.csvimport.*;
 import de.deadlocker8.budgetmaster.utils.Mappings;
+import de.deadlocker8.budgetmaster.utils.ResourceNotFoundException;
 import de.deadlocker8.budgetmaster.utils.WebRequestUtils;
 import de.deadlocker8.budgetmaster.utils.notification.Notification;
 import de.deadlocker8.budgetmaster.utils.notification.NotificationType;
@@ -32,6 +33,8 @@ public class TransactionImportController extends BaseController
 		public static final String ERROR = "error";
 		public static final String CATEGORIES = "categories";
 		public static final String CSV_IMPORT_SETTINGS = "csvImportSettings";
+		public static final String CSV_TRANSACTION = "csvTransaction";
+		public static final String CSV_TRANSACTION_INDEX = "csvTransactionIndex";
 	}
 
 	private static class ReturnValues
@@ -41,6 +44,7 @@ public class TransactionImportController extends BaseController
 		public static final String REDIRECT_CANCEL = "redirect:/transactionImport/cancel";
 		public static final String NEW_TRANSACTION_NORMAL = "transactions/newTransactionNormal";
 		public static final String NEW_TRANSACTION_TRANSFER = "transactions/newTransactionTransfer";
+		public static final String TRANSACTION_IMPORT_ROW = "transactions/transactionImportRow";
 		public static final String REDIRECT_TEMPLATES = "redirect:/templates";
 	}
 
@@ -201,29 +205,41 @@ public class TransactionImportController extends BaseController
 	}
 
 	@GetMapping("/{index}/skip")
-	public String skip(WebRequest request, @PathVariable("index") Integer index)
+	public String skip(Model model, WebRequest request, @PathVariable("index") Integer index)
 	{
 		final Optional<CsvTransaction> transactionOptional = getTransactionByIndex(request, index);
 		if(transactionOptional.isEmpty())
 		{
-			return ReturnValues.REDIRECT_IMPORT;
+			throw new ResourceNotFoundException();
 		}
 
-		transactionOptional.get().setStatus(CsvTransactionStatus.SKIPPED);
-		return ReturnValues.REDIRECT_IMPORT;
+		final CsvTransaction csvTransaction = transactionOptional.get();
+		csvTransaction.setStatus(CsvTransactionStatus.SKIPPED);
+
+		model.addAttribute(ModelAttributes.CATEGORIES, categoryService.getAllEntitiesAsc());
+		model.addAttribute(TransactionModelAttributes.SUGGESTIONS_JSON, transactionService.getNameSuggestionsJson());
+		model.addAttribute(ModelAttributes.CSV_TRANSACTION, csvTransaction);
+		model.addAttribute(ModelAttributes.CSV_TRANSACTION_INDEX, index);
+		return ReturnValues.TRANSACTION_IMPORT_ROW;
 	}
 
 	@GetMapping("/{index}/undoSkip")
-	public String undoSkip(WebRequest request, @PathVariable("index") Integer index)
+	public String undoSkip(Model model, WebRequest request, @PathVariable("index") Integer index)
 	{
 		final Optional<CsvTransaction> transactionOptional = getTransactionByIndex(request, index);
 		if(transactionOptional.isEmpty())
 		{
-			return ReturnValues.REDIRECT_IMPORT;
+			throw new ResourceNotFoundException();
 		}
 
-		transactionOptional.get().setStatus(CsvTransactionStatus.PENDING);
-		return ReturnValues.REDIRECT_IMPORT;
+		final CsvTransaction csvTransaction = transactionOptional.get();
+		csvTransaction.setStatus(CsvTransactionStatus.PENDING);
+
+		model.addAttribute(ModelAttributes.CATEGORIES, categoryService.getAllEntitiesAsc());
+		model.addAttribute(TransactionModelAttributes.SUGGESTIONS_JSON, transactionService.getNameSuggestionsJson());
+		model.addAttribute(ModelAttributes.CSV_TRANSACTION, csvTransaction);
+		model.addAttribute(ModelAttributes.CSV_TRANSACTION_INDEX, index);
+		return ReturnValues.TRANSACTION_IMPORT_ROW;
 	}
 
 	@GetMapping("/{index}/newTransaction/{type}")
@@ -309,6 +325,13 @@ public class TransactionImportController extends BaseController
 		}
 
 		final List<CsvTransaction> csvTransactions = (List<CsvTransaction>) attribute;
-		return Optional.of(csvTransactions.get(index));
+		try
+		{
+			return Optional.of(csvTransactions.get(index));
+		}
+		catch(IndexOutOfBoundsException e)
+		{
+			return Optional.empty();
+		}
 	}
 }
\ No newline at end of file
diff --git a/BudgetMasterServer/src/main/resources/static/js/transactionImport.js b/BudgetMasterServer/src/main/resources/static/js/transactionImport.js
index 9dbca0238..e4c29c1ec 100644
--- a/BudgetMasterServer/src/main/resources/static/js/transactionImport.js
+++ b/BudgetMasterServer/src/main/resources/static/js/transactionImport.js
@@ -32,4 +32,54 @@ $(document).ready(function()
             autoCompleteInstances[i].dropdown.dropdownEl.tabIndex = -1;
         }
     }
-});
\ No newline at end of file
+
+    initCsvTransactionButtons();
+});
+
+function initCsvTransactionButtons()
+{
+    const buttonsSkip = document.getElementsByClassName('button-request-transaction-import-skip');
+    for(let i = 0; i < buttonsSkip.length; i++)
+    {
+        const button = buttonsSkip[i];
+        button.addEventListener('click', function()
+        {
+            performCsvTransactionGetRequestWithoutReload(button, 'Error skipping transaction');
+        });
+    }
+
+    const buttonsUndoSkip = document.getElementsByClassName('button-request-transaction-import-undo-skip');
+    for(let i = 0; i < buttonsUndoSkip.length; i++)
+    {
+        const button = buttonsUndoSkip[i];
+        button.addEventListener('click', function()
+        {
+            performCsvTransactionGetRequestWithoutReload(button, 'Error undo skip transaction');
+        });
+    }
+}
+
+function performCsvTransactionGetRequestWithoutReload(button, errorMessage)
+{
+    const url = button.dataset.url;
+    const csvTransactionId = button.dataset.index;
+
+    $.ajax({
+        type: 'GET',
+        url: url,
+        data: {},
+        success: function(data)
+        {
+            $('#transaction-import-row-' + csvTransactionId).replaceWith(data);
+            initCsvTransactionButtons();
+        },
+        error: function(response)
+        {
+            M.toast({
+                html: errorMessage,
+                classes: 'red'
+            });
+            console.error(response);
+        }
+    });
+}
diff --git a/BudgetMasterServer/src/main/resources/templates/helpers/header.ftl b/BudgetMasterServer/src/main/resources/templates/helpers/header.ftl
index b5bf1ddc7..094800aa0 100644
--- a/BudgetMasterServer/src/main/resources/templates/helpers/header.ftl
+++ b/BudgetMasterServer/src/main/resources/templates/helpers/header.ftl
@@ -136,9 +136,10 @@
     </button>
 </#macro>
 
-<#macro buttonFlat url icon localizationKey id="" classes="" isDataUrl=false noUrl=false iconClasses='' target=''>
+<#macro buttonFlat url icon localizationKey id="" classes="" isDataUrl=false noUrl=false iconClasses='' target='' datasetIndex=''>
     <a <#if target?has_content>target="${target}"</#if> <#if !isDataUrl && !noUrl>href="<@s.url url/>"</#if>
        id="${id}"
+       <#if datasetIndex?has_content>data-index="${datasetIndex}"</#if>
        class="waves-effect waves-light btn-flat ${classes}"
             <#if isDataUrl>data-url="${url}"</#if>>
         <i class="material-icons left <#if !localizationKey?has_content>no-margin</#if> ${iconClasses}">${icon}</i><#if localizationKey?has_content><span>${locale.getString(localizationKey)}</span></#if>
diff --git a/BudgetMasterServer/src/main/resources/templates/transactions/transactionImportMacros.ftl b/BudgetMasterServer/src/main/resources/templates/transactions/transactionImportMacros.ftl
index a725ea86f..fae7d580a 100644
--- a/BudgetMasterServer/src/main/resources/templates/transactions/transactionImportMacros.ftl
+++ b/BudgetMasterServer/src/main/resources/templates/transactions/transactionImportMacros.ftl
@@ -1,4 +1,5 @@
 <#import "../helpers/header.ftl" as header>
+<@header.globals/>
 <#import "../helpers/validation.ftl" as validation>
 <#import "/spring.ftl" as s>
 
@@ -187,7 +188,7 @@
 </#macro>
 
 <#macro renderCsvTransaction csvTransaction index>
-    <tr class="transaction-import-row <#if csvTransaction.getStatus().name() == 'SKIPPED'>transaction-import-row-skipped</#if>">
+    <tr class="transaction-import-row <#if csvTransaction.getStatus().name() == 'SKIPPED'>transaction-import-row-skipped</#if>" id="transaction-import-row-${index}">
         <form name="NewTransactionInPlace" method="POST" action="<@s.url '/transactionImport/' + index + '/newTransactionInPlace'/>">
             <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
             <td data-order="${locale.getString(csvTransaction.getStatus().getLocalizationKey())}" data-search="${locale.getString(csvTransaction.getStatus().getLocalizationKey())}"><@statusBanner csvTransaction.getStatus()/></td>
@@ -208,7 +209,7 @@
             <td data-order="${currencyService.getCurrencyString(csvTransaction.getAmount())}" data-search="${currencyService.getCurrencyString(csvTransaction.getAmount())}">${currencyService.getCurrencyString(csvTransaction.getAmount())}</td>
             <td>
                 <#if csvTransaction.getStatus().name() == 'SKIPPED'>
-                    <@header.buttonFlat url='/transactionImport/' + index + '/undoSkip' isDataUrl=true icon='do_disturb_off' localizationKey='' classes="no-padding text-default button-request-transaction-import-undo-skip"/>
+                    <@header.buttonFlat url='/transactionImport/' + index + '/undoSkip' isDataUrl=true icon='do_disturb_off' localizationKey='' classes="no-padding text-default button-request-transaction-import-undo-skip" datasetIndex=index/>
                 <#else>
                     <@header.buttonSubmit name='action' icon='save' localizationKey='' classes='text-white'/>&nbsp;
                     <div class="fixed-action-btn edit-transaction-button">
@@ -230,7 +231,7 @@
                             </li>
                         </ul>
                     </div>
-                    <@header.buttonFlat url='/transactionImport/' + index + '/skip' isDataUrl=true icon='do_not_disturb_on' localizationKey='' classes="no-padding text-default button-request-transaction-import-skip"/>
+                    <@header.buttonFlat url='/transactionImport/' + index + '/skip' isDataUrl=true icon='do_not_disturb_on' localizationKey='' classes="no-padding text-default button-request-transaction-import-skip" datasetIndex=index/>
                 </#if>
             </td>
         </form>
diff --git a/BudgetMasterServer/src/main/resources/templates/transactions/transactionImportRow.ftl b/BudgetMasterServer/src/main/resources/templates/transactions/transactionImportRow.ftl
new file mode 100644
index 000000000..7b47dfbec
--- /dev/null
+++ b/BudgetMasterServer/src/main/resources/templates/transactions/transactionImportRow.ftl
@@ -0,0 +1,3 @@
+<#import "transactionImportMacros.ftl" as transactionImportMacros>
+
+<@transactionImportMacros.renderCsvTransaction csvTransaction csvTransactionIndex/>
\ No newline at end of file
-- 
GitLab