From 0a15d09db4f9b71df01963b404f571e253efc35a Mon Sep 17 00:00:00 2001 From: AJ Date: Sun, 16 Nov 2025 11:54:00 -0500 Subject: [PATCH] Refactor: separate JavaScript from HTML into external files Extracted all inline JavaScript from Razor pages into dedicated external files for better code organization and browser caching: - upload.js: transaction preview, pagination, form handling - transactions.js: date range filters, Bootstrap tooltips - edit-transaction.js: category/merchant dropdowns, copy functionality - merchants.js: modal handling for merchant management - category-mappings.js: modal handling for category rules Updated all affected .cshtml files to reference external scripts instead of inline script blocks. This improves maintainability, enables browser caching, and provides better separation of concerns. --- MoneyMap/Pages/CategoryMappings.cshtml | 70 +------------- MoneyMap/Pages/EditTransaction.cshtml | 72 +------------- MoneyMap/Pages/Merchants.cshtml | 32 +------ MoneyMap/Pages/Transactions.cshtml | 43 +-------- MoneyMap/Pages/Upload.cshtml | 113 +--------------------- MoneyMap/wwwroot/js/category-mappings.js | 69 ++++++++++++++ MoneyMap/wwwroot/js/edit-transaction.js | 73 ++++++++++++++ MoneyMap/wwwroot/js/merchants.js | 31 ++++++ MoneyMap/wwwroot/js/transactions.js | 44 +++++++++ MoneyMap/wwwroot/js/upload.js | 116 +++++++++++++++++++++++ 10 files changed, 341 insertions(+), 322 deletions(-) create mode 100644 MoneyMap/wwwroot/js/category-mappings.js create mode 100644 MoneyMap/wwwroot/js/edit-transaction.js create mode 100644 MoneyMap/wwwroot/js/merchants.js create mode 100644 MoneyMap/wwwroot/js/transactions.js create mode 100644 MoneyMap/wwwroot/js/upload.js diff --git a/MoneyMap/Pages/CategoryMappings.cshtml b/MoneyMap/Pages/CategoryMappings.cshtml index a975a1f..a6ba96c 100644 --- a/MoneyMap/Pages/CategoryMappings.cshtml +++ b/MoneyMap/Pages/CategoryMappings.cshtml @@ -304,73 +304,5 @@ else @section Scripts { - + } diff --git a/MoneyMap/Pages/EditTransaction.cshtml b/MoneyMap/Pages/EditTransaction.cshtml index dfdc8b9..defed2f 100644 --- a/MoneyMap/Pages/EditTransaction.cshtml +++ b/MoneyMap/Pages/EditTransaction.cshtml @@ -263,75 +263,11 @@ @section Scripts { + } - - - diff --git a/MoneyMap/Pages/Merchants.cshtml b/MoneyMap/Pages/Merchants.cshtml index 61b4eb5..513bb0c 100644 --- a/MoneyMap/Pages/Merchants.cshtml +++ b/MoneyMap/Pages/Merchants.cshtml @@ -152,35 +152,5 @@ else @section Scripts { - + } diff --git a/MoneyMap/Pages/Transactions.cshtml b/MoneyMap/Pages/Transactions.cshtml index 8deda2a..ba9d51c 100644 --- a/MoneyMap/Pages/Transactions.cshtml +++ b/MoneyMap/Pages/Transactions.cshtml @@ -304,47 +304,6 @@ else } @section Scripts { - + } diff --git a/MoneyMap/Pages/Upload.cshtml b/MoneyMap/Pages/Upload.cshtml index f517b99..aea6bed 100644 --- a/MoneyMap/Pages/Upload.cshtml +++ b/MoneyMap/Pages/Upload.cshtml @@ -235,116 +235,5 @@ else @section Scripts { - + } diff --git a/MoneyMap/wwwroot/js/category-mappings.js b/MoneyMap/wwwroot/js/category-mappings.js new file mode 100644 index 0000000..b240db4 --- /dev/null +++ b/MoneyMap/wwwroot/js/category-mappings.js @@ -0,0 +1,69 @@ +// Category Mappings Page JavaScript + +function openEditModal(id, category, pattern, priority, merchant) { + console.log('Opening modal for:', id, category, pattern, priority, merchant); + + document.getElementById('editId').value = id; + document.getElementById('editCategory').value = category; + document.getElementById('editPattern').value = pattern; + document.getElementById('editPriority').value = priority; + document.getElementById('editMerchant').value = merchant || ''; + + var modalElement = document.getElementById('editModal'); + if (modalElement && typeof bootstrap !== 'undefined') { + var modal = new bootstrap.Modal(modalElement); + modal.show(); + } else { + console.error('Bootstrap modal not available'); + alert('Error: Modal system not loaded. Please refresh the page.'); + } +} + +document.addEventListener('DOMContentLoaded', function() { + + // Debug: Log form values before submission + var addForm = document.querySelector('#addModal form'); + if (addForm) { + addForm.addEventListener('submit', function(e) { + var categoryInput = document.getElementById('addCategory'); + var patternInput = document.getElementById('addPattern'); + var priorityInput = document.getElementById('addPriority'); + + console.log('=== ADD FORM SUBMISSION ==='); + console.log('Category:', categoryInput.value, 'Name:', categoryInput.name); + console.log('Pattern:', patternInput.value, 'Name:', patternInput.name); + console.log('Priority:', priorityInput.value, 'Name:', priorityInput.name); + console.log('Form Data:'); + var formData = new FormData(addForm); + for (var pair of formData.entries()) { + console.log(' ' + pair[0] + ': ' + pair[1]); + } + }); + } + + var editForm = document.querySelector('#editModal form'); + if (editForm) { + editForm.addEventListener('submit', function(e) { + var id = document.getElementById('editId').value; + var category = document.getElementById('editCategory').value; + var pattern = document.getElementById('editPattern').value; + var priority = document.getElementById('editPriority').value; + console.log('Submitting Edit form:', { id, category, pattern, priority }); + console.log('Category input name:', document.getElementById('editCategory').name); + }); + } + + // Reopen modals if there are validation errors + var addCategoryInput = document.getElementById('addCategory'); + var editCategoryInput = document.getElementById('editCategory'); + + if (addCategoryInput && addCategoryInput.classList.contains('input-validation-error')) { + var addModal = new bootstrap.Modal(document.getElementById('addModal')); + addModal.show(); + } + + if (editCategoryInput && editCategoryInput.classList.contains('input-validation-error')) { + var editModal = new bootstrap.Modal(document.getElementById('editModal')); + editModal.show(); + } +}); diff --git a/MoneyMap/wwwroot/js/edit-transaction.js b/MoneyMap/wwwroot/js/edit-transaction.js new file mode 100644 index 0000000..d1b1eb5 --- /dev/null +++ b/MoneyMap/wwwroot/js/edit-transaction.js @@ -0,0 +1,73 @@ +// Edit Transaction Page JavaScript + +function handleCategoryChange() { + const select = document.getElementById('categorySelect'); + const customInputDiv = document.getElementById('customCategoryInput'); + const categoryInput = document.getElementById('categoryInput'); + + if (select.value === '__custom__') { + customInputDiv.style.display = 'block'; + categoryInput.value = ''; + categoryInput.focus(); + } else { + customInputDiv.style.display = 'none'; + categoryInput.value = select.value; + } +} + +function handleMerchantChange() { + const select = document.getElementById('merchantSelect'); + const customInput = document.getElementById('customMerchantInput'); + const hiddenInput = document.getElementById('merchantHidden'); + const merchantNameInput = document.querySelector('input[name="Transaction.MerchantName"]'); + + if (select.value === '__custom__') { + customInput.style.display = 'block'; + hiddenInput.value = ''; + merchantNameInput.value = ''; + merchantNameInput.focus(); + } else { + customInput.style.display = 'none'; + hiddenInput.value = select.value; + merchantNameInput.value = ''; + } +} + +function copyTransactionId(transactionId) { + if (navigator.clipboard && window.isSecureContext) { + navigator.clipboard.writeText(transactionId.toString()); + } else { + var ta = document.createElement('textarea'); + ta.value = transactionId; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); + } + var btns = document.querySelectorAll('button[onclick*="copyTransactionId"]'); + btns.forEach(function(btn) { + var old = btn.textContent; + btn.textContent = 'Copied!'; + setTimeout(function() { + btn.textContent = old; + }, 1500); + }); +} + +// Update hidden field when custom input changes +document.addEventListener('DOMContentLoaded', function() { + const categoryInput = document.querySelector('input[name="Transaction.Category"]'); + const categorySelect = document.getElementById('categorySelect'); + + if (categoryInput) { + categoryInput.addEventListener('input', function() { + if (categorySelect.value === '__custom__') { + // Keep custom selected when typing + } + }); + } + + // Initialize on page load + handleCategoryChange(); + handleMerchantChange(); +}); diff --git a/MoneyMap/wwwroot/js/merchants.js b/MoneyMap/wwwroot/js/merchants.js new file mode 100644 index 0000000..a684a92 --- /dev/null +++ b/MoneyMap/wwwroot/js/merchants.js @@ -0,0 +1,31 @@ +// Merchants Page JavaScript + +function openEditModal(id, name) { + document.getElementById('editId').value = id; + document.getElementById('editName').value = name; + + var modalElement = document.getElementById('editModal'); + if (modalElement && typeof bootstrap !== 'undefined') { + var modal = new bootstrap.Modal(modalElement); + modal.show(); + } else { + console.error('Bootstrap modal not available'); + alert('Error: Modal system not loaded. Please refresh the page.'); + } +} + +document.addEventListener('DOMContentLoaded', function() { + // Reopen modals if there are validation errors + var addNameInput = document.getElementById('addName'); + var editNameInput = document.getElementById('editName'); + + if (addNameInput && addNameInput.classList.contains('input-validation-error')) { + var addModal = new bootstrap.Modal(document.getElementById('addModal')); + addModal.show(); + } + + if (editNameInput && editNameInput.classList.contains('input-validation-error')) { + var editModal = new bootstrap.Modal(document.getElementById('editModal')); + editModal.show(); + } +}); diff --git a/MoneyMap/wwwroot/js/transactions.js b/MoneyMap/wwwroot/js/transactions.js new file mode 100644 index 0000000..93e7c89 --- /dev/null +++ b/MoneyMap/wwwroot/js/transactions.js @@ -0,0 +1,44 @@ +// Transactions Page JavaScript + +// Initialize Bootstrap tooltips for notes badges +document.addEventListener('DOMContentLoaded', function() { + var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); + var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { + return new bootstrap.Tooltip(tooltipTriggerEl); + }); +}); + +// Quick date range functions +function formatDate(date) { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +} + +function setDateRange(days) { + const endDate = new Date(); + const startDate = new Date(); + startDate.setDate(startDate.getDate() - days); + + document.getElementById('startDateInput').value = formatDate(startDate); + document.getElementById('endDateInput').value = formatDate(endDate); +} + +function setDateRangeThisMonth() { + const now = new Date(); + const startDate = new Date(now.getFullYear(), now.getMonth(), 1); + const endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0); + + document.getElementById('startDateInput').value = formatDate(startDate); + document.getElementById('endDateInput').value = formatDate(endDate); +} + +function setDateRangeLastMonth() { + const now = new Date(); + const startDate = new Date(now.getFullYear(), now.getMonth() - 1, 1); + const endDate = new Date(now.getFullYear(), now.getMonth(), 0); + + document.getElementById('startDateInput').value = formatDate(startDate); + document.getElementById('endDateInput').value = formatDate(endDate); +} diff --git a/MoneyMap/wwwroot/js/upload.js b/MoneyMap/wwwroot/js/upload.js new file mode 100644 index 0000000..8604fa1 --- /dev/null +++ b/MoneyMap/wwwroot/js/upload.js @@ -0,0 +1,116 @@ +// Upload Page JavaScript +let currentPage = 0; +const pageSize = 100; +let totalRows = 0; + +function togglePaymentSelection() { + const mode = document.querySelector('input[name="PaymentMode"]:checked').value; + const cardRow = document.getElementById('cardSelectRow'); + const accountRow = document.getElementById('accountSelectRow'); + + cardRow.style.display = mode === 'Card' ? 'block' : 'none'; + accountRow.style.display = mode === 'Account' ? 'block' : 'none'; +} + +function toggleAllCheckboxes() { + const selectAll = document.getElementById('selectAll'); + const checkboxes = document.querySelectorAll('.transaction-checkbox'); + checkboxes.forEach(cb => cb.checked = selectAll.checked); + updateSelectedCount(); +} + +function updateSelectedCount() { + const checkboxes = document.querySelectorAll('.transaction-checkbox:checked'); + const count = checkboxes.length; + const selectedCountEl = document.getElementById('selectedCount'); + const selectedCountButtonEl = document.getElementById('selectedCountButton'); + if (selectedCountEl) selectedCountEl.textContent = count; + if (selectedCountButtonEl) selectedCountButtonEl.textContent = count; +} + +function updateAllTransactionAccounts() { + // Called when the global account dropdown changes + // No action needed - we'll read the global account on form submit +} + +function showPage(pageNum) { + const rows = document.querySelectorAll('.transaction-row'); + const start = pageNum * pageSize; + const end = start + pageSize; + + rows.forEach((row, index) => { + row.style.display = (index >= start && index < end) ? '' : 'none'; + }); + + // Update pagination display + const pageStartEl = document.getElementById('pageStart'); + const pageEndEl = document.getElementById('pageEnd'); + if (pageStartEl && pageEndEl) { + pageStartEl.textContent = start + 1; + pageEndEl.textContent = Math.min(end, totalRows); + } + + currentPage = pageNum; +} + +function nextPage() { + const maxPage = Math.ceil(totalRows / pageSize) - 1; + if (currentPage < maxPage) { + showPage(currentPage + 1); + } +} + +function previousPage() { + if (currentPage > 0) { + showPage(currentPage - 1); + } +} + +function prepareFormData() { + // Collect selected indices + const selectedIndices = []; + document.querySelectorAll('.transaction-checkbox:checked').forEach(cb => { + selectedIndices.push(cb.getAttribute('data-index')); + }); + document.getElementById('selectedIndices').value = selectedIndices.join(','); + + // Get the global account ID + const globalAccountId = document.getElementById('globalAccountId')?.value; + if (!globalAccountId) { + alert('Please select an account for all transactions'); + return false; + } + + // Collect payment data (account + optional card + category per transaction) + const paymentData = {}; + document.querySelectorAll('.card-select').forEach((select) => { + const index = select.getAttribute('data-index'); + const cardId = select.value ? parseInt(select.value) : null; + + // Get category for this transaction + const categoryInput = document.querySelector(`.category-input[data-index="${index}"]`); + const category = categoryInput ? categoryInput.value.trim() : ''; + + paymentData[index] = { + AccountId: parseInt(globalAccountId), + CardId: cardId, + Category: category + }; + }); + document.getElementById('paymentData').value = JSON.stringify(paymentData); + + return true; // Allow form submission +} + +// Initialize on page load +document.addEventListener('DOMContentLoaded', function() { + togglePaymentSelection(); + + // Get total rows from the preview transactions count + const previewCount = document.querySelectorAll('.transaction-row').length; + totalRows = previewCount; + + if (totalRows > pageSize) { + showPage(0); + } +});