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.
This commit is contained in:
AJ
2025-11-16 11:54:00 -05:00
parent 1c28d9cc88
commit 0a15d09db4
10 changed files with 341 additions and 322 deletions

View File

@@ -304,73 +304,5 @@ else
</div>
@section Scripts {
<script>
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();
}
});
</script>
<script src="~/js/category-mappings.js"></script>
}

View File

@@ -263,75 +263,11 @@
@section Scripts {
<partial name="_ValidationScriptsPartial" />
<script src="~/js/edit-transaction.js"></script>
<script>
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;
// Override with server-side transaction ID
function copyTransactionId() {
window.copyTransactionId('@Model.Transaction.Id');
}
}
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 = '';
}
}
// 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();
});
</script>
}
<script>
function copyTransactionId() {
var id = '@Model.Transaction.Id';
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(id.toString());
} else {
var ta = document.createElement('textarea');
ta.value = id;
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); });
}
</script>

View File

@@ -152,35 +152,5 @@ else
</div>
@section Scripts {
<script>
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();
}
});
</script>
<script src="~/js/merchants.js"></script>
}

View File

@@ -304,47 +304,6 @@ else
}
@section Scripts {
<script>
// Initialize Bootstrap tooltips for notes badges
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);
}
</script>
<script src="~/js/transactions.js"></script>
}

View File

@@ -235,116 +235,5 @@ else
@section Scripts {
<partial name="_ValidationScriptsPartial" />
<script>
let currentPage = 0;
const pageSize = 100;
const totalRows = @Model.PreviewTransactions.Count;
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();
if (totalRows > pageSize) {
showPage(0);
}
});
</script>
<script src="~/js/upload.js"></script>
}

View File

@@ -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();
}
});

View File

@@ -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();
});

View File

@@ -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();
}
});

View File

@@ -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);
}

View File

@@ -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);
}
});