489 lines
14 KiB
JavaScript
489 lines
14 KiB
JavaScript
/**
|
|
* Keyboard Shortcuts for Delphi Consulting Group Database System
|
|
* Replicates legacy Pascal system shortcuts for user familiarity
|
|
*/
|
|
|
|
let keyboardShortcutsEnabled = true;
|
|
|
|
function initializeKeyboardShortcuts() {
|
|
document.addEventListener('keydown', handleKeyboardShortcuts);
|
|
console.log('Keyboard shortcuts initialized');
|
|
}
|
|
|
|
function handleKeyboardShortcuts(event) {
|
|
if (!keyboardShortcutsEnabled) {
|
|
return;
|
|
}
|
|
|
|
// Don't process shortcuts if user is typing in input fields
|
|
const activeElement = document.activeElement;
|
|
const isInputField = ['INPUT', 'TEXTAREA', 'SELECT'].includes(activeElement.tagName) ||
|
|
activeElement.contentEditable === 'true';
|
|
|
|
// Allow specific shortcuts even in input fields
|
|
const allowedInInputs = ['F1', 'Escape'];
|
|
const keyName = getKeyName(event);
|
|
|
|
if (isInputField && !allowedInInputs.includes(keyName)) {
|
|
return;
|
|
}
|
|
|
|
// Handle shortcuts based on key combination
|
|
const shortcut = getShortcutKey(event);
|
|
|
|
switch (shortcut) {
|
|
// Help
|
|
case 'F1':
|
|
event.preventDefault();
|
|
showHelp();
|
|
break;
|
|
|
|
// Navigation shortcuts
|
|
case 'Alt+C':
|
|
event.preventDefault();
|
|
navigateTo('/customers');
|
|
break;
|
|
case 'Alt+F':
|
|
event.preventDefault();
|
|
navigateTo('/files');
|
|
break;
|
|
case 'Alt+L':
|
|
event.preventDefault();
|
|
navigateTo('/financial');
|
|
break;
|
|
case 'Alt+D':
|
|
event.preventDefault();
|
|
navigateTo('/documents');
|
|
break;
|
|
case 'Alt+A':
|
|
event.preventDefault();
|
|
navigateTo('/admin');
|
|
break;
|
|
|
|
// Global search
|
|
case 'Ctrl+F':
|
|
event.preventDefault();
|
|
focusGlobalSearch();
|
|
break;
|
|
|
|
// Form shortcuts
|
|
case 'Ctrl+N':
|
|
event.preventDefault();
|
|
newRecord();
|
|
break;
|
|
case 'Ctrl+S':
|
|
event.preventDefault();
|
|
saveRecord();
|
|
break;
|
|
case 'F9':
|
|
event.preventDefault();
|
|
editMode();
|
|
break;
|
|
case 'F2':
|
|
event.preventDefault();
|
|
completeAction();
|
|
break;
|
|
case 'F8':
|
|
event.preventDefault();
|
|
clearForm();
|
|
break;
|
|
case 'Delete':
|
|
if (!isInputField) {
|
|
event.preventDefault();
|
|
deleteRecord();
|
|
}
|
|
break;
|
|
case 'Escape':
|
|
event.preventDefault();
|
|
cancelAction();
|
|
break;
|
|
|
|
// Legacy system shortcuts
|
|
case 'F10':
|
|
event.preventDefault();
|
|
showMenu();
|
|
break;
|
|
case 'Alt+M':
|
|
event.preventDefault();
|
|
showMemo();
|
|
break;
|
|
case 'Alt+T':
|
|
event.preventDefault();
|
|
toggleTimer();
|
|
break;
|
|
case 'Alt+B':
|
|
event.preventDefault();
|
|
showBalanceSummary();
|
|
break;
|
|
|
|
// Quick creation shortcuts
|
|
case 'Ctrl+Shift+C':
|
|
event.preventDefault();
|
|
newCustomer();
|
|
break;
|
|
case 'Ctrl+Shift+F':
|
|
event.preventDefault();
|
|
newFile();
|
|
break;
|
|
case 'Ctrl+Shift+T':
|
|
event.preventDefault();
|
|
newTransaction();
|
|
break;
|
|
|
|
// Date navigation (legacy system feature)
|
|
case '+':
|
|
if (!isInputField && isDateField(activeElement)) {
|
|
event.preventDefault();
|
|
changeDateBy(1);
|
|
}
|
|
break;
|
|
case '-':
|
|
if (!isInputField && isDateField(activeElement)) {
|
|
event.preventDefault();
|
|
changeDateBy(-1);
|
|
}
|
|
break;
|
|
|
|
// Table navigation
|
|
case 'ArrowUp':
|
|
if (!isInputField && isInTable()) {
|
|
event.preventDefault();
|
|
navigateTable('up');
|
|
}
|
|
break;
|
|
case 'ArrowDown':
|
|
if (!isInputField && isInTable()) {
|
|
event.preventDefault();
|
|
navigateTable('down');
|
|
}
|
|
break;
|
|
case 'PageUp':
|
|
if (!isInputField && isInTable()) {
|
|
event.preventDefault();
|
|
navigateTable('pageup');
|
|
}
|
|
break;
|
|
case 'PageDown':
|
|
if (!isInputField && isInTable()) {
|
|
event.preventDefault();
|
|
navigateTable('pagedown');
|
|
}
|
|
break;
|
|
case 'Home':
|
|
if (!isInputField && isInTable()) {
|
|
event.preventDefault();
|
|
navigateTable('home');
|
|
}
|
|
break;
|
|
case 'End':
|
|
if (!isInputField && isInTable()) {
|
|
event.preventDefault();
|
|
navigateTable('end');
|
|
}
|
|
break;
|
|
case 'Enter':
|
|
if (!isInputField && isInTable()) {
|
|
event.preventDefault();
|
|
openRecord();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
function getShortcutKey(event) {
|
|
const parts = [];
|
|
|
|
if (event.ctrlKey) parts.push('Ctrl');
|
|
if (event.altKey) parts.push('Alt');
|
|
if (event.shiftKey) parts.push('Shift');
|
|
|
|
let key = event.key;
|
|
|
|
// Handle special keys
|
|
switch (event.keyCode) {
|
|
case 112: key = 'F1'; break;
|
|
case 113: key = 'F2'; break;
|
|
case 114: key = 'F3'; break;
|
|
case 115: key = 'F4'; break;
|
|
case 116: key = 'F5'; break;
|
|
case 117: key = 'F6'; break;
|
|
case 118: key = 'F7'; break;
|
|
case 119: key = 'F8'; break;
|
|
case 120: key = 'F9'; break;
|
|
case 121: key = 'F10'; break;
|
|
case 122: key = 'F11'; break;
|
|
case 123: key = 'F12'; break;
|
|
case 46: key = 'Delete'; break;
|
|
case 27: key = 'Escape'; break;
|
|
case 33: key = 'PageUp'; break;
|
|
case 34: key = 'PageDown'; break;
|
|
case 35: key = 'End'; break;
|
|
case 36: key = 'Home'; break;
|
|
case 37: key = 'ArrowLeft'; break;
|
|
case 38: key = 'ArrowUp'; break;
|
|
case 39: key = 'ArrowRight'; break;
|
|
case 40: key = 'ArrowDown'; break;
|
|
case 13: key = 'Enter'; break;
|
|
case 187: key = '+'; break; // Plus key
|
|
case 189: key = '-'; break; // Minus key
|
|
}
|
|
|
|
parts.push(key);
|
|
return parts.join('+');
|
|
}
|
|
|
|
function getKeyName(event) {
|
|
switch (event.keyCode) {
|
|
case 112: return 'F1';
|
|
case 27: return 'Escape';
|
|
default: return event.key;
|
|
}
|
|
}
|
|
|
|
// Navigation functions
|
|
function navigateTo(url) {
|
|
window.location.href = url;
|
|
}
|
|
|
|
function focusGlobalSearch() {
|
|
const searchInput = document.querySelector('#global-search, .search-input, [name="search"]');
|
|
if (searchInput) {
|
|
searchInput.focus();
|
|
searchInput.select();
|
|
} else {
|
|
navigateTo('/search');
|
|
}
|
|
}
|
|
|
|
// Form action functions
|
|
function newRecord() {
|
|
const newBtn = document.querySelector('.btn-new, [data-action="new"], .btn-primary[href*="new"]');
|
|
if (newBtn) {
|
|
newBtn.click();
|
|
} else {
|
|
showToast('New record shortcut not available on this page', 'info');
|
|
}
|
|
}
|
|
|
|
function saveRecord() {
|
|
const saveBtn = document.querySelector('.btn-save, [data-action="save"], .btn-success[type="submit"]');
|
|
if (saveBtn) {
|
|
saveBtn.click();
|
|
} else {
|
|
// Try to submit the main form
|
|
const form = document.querySelector('form.main-form, form');
|
|
if (form) {
|
|
form.submit();
|
|
} else {
|
|
showToast('Save shortcut not available on this page', 'info');
|
|
}
|
|
}
|
|
}
|
|
|
|
function editMode() {
|
|
const editBtn = document.querySelector('.btn-edit, [data-action="edit"]');
|
|
if (editBtn) {
|
|
editBtn.click();
|
|
} else {
|
|
showToast('Edit mode shortcut not available on this page', 'info');
|
|
}
|
|
}
|
|
|
|
function completeAction() {
|
|
const completeBtn = document.querySelector('.btn-complete, [data-action="complete"], .btn-primary');
|
|
if (completeBtn) {
|
|
completeBtn.click();
|
|
} else {
|
|
saveRecord(); // Fallback to save
|
|
}
|
|
}
|
|
|
|
function clearForm() {
|
|
const clearBtn = document.querySelector('.btn-clear, [data-action="clear"]');
|
|
if (clearBtn) {
|
|
clearBtn.click();
|
|
} else {
|
|
// Clear all form inputs
|
|
const form = document.querySelector('form');
|
|
if (form) {
|
|
form.reset();
|
|
showToast('Form cleared', 'info');
|
|
}
|
|
}
|
|
}
|
|
|
|
function deleteRecord() {
|
|
const deleteBtn = document.querySelector('.btn-delete, [data-action="delete"], .btn-danger');
|
|
if (deleteBtn) {
|
|
deleteBtn.click();
|
|
} else {
|
|
showToast('Delete shortcut not available on this page', 'info');
|
|
}
|
|
}
|
|
|
|
function cancelAction() {
|
|
// Close modals first
|
|
const modal = document.querySelector('.modal.show');
|
|
if (modal) {
|
|
const bsModal = bootstrap.Modal.getInstance(modal);
|
|
if (bsModal) {
|
|
bsModal.hide();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Then try cancel buttons
|
|
const cancelBtn = document.querySelector('.btn-cancel, [data-action="cancel"], .btn-secondary');
|
|
if (cancelBtn) {
|
|
cancelBtn.click();
|
|
} else {
|
|
window.history.back();
|
|
}
|
|
}
|
|
|
|
// Legacy system specific functions
|
|
function showHelp() {
|
|
const helpModal = document.querySelector('#shortcutsModal');
|
|
if (helpModal) {
|
|
const modal = new bootstrap.Modal(helpModal);
|
|
modal.show();
|
|
} else {
|
|
showToast('Press F1 to see keyboard shortcuts', 'info');
|
|
}
|
|
}
|
|
|
|
function showMenu() {
|
|
// Toggle main navigation menu on mobile or show dropdown
|
|
const navbarToggler = document.querySelector('.navbar-toggler');
|
|
if (navbarToggler && !navbarToggler.classList.contains('collapsed')) {
|
|
navbarToggler.click();
|
|
} else {
|
|
showToast('Menu (F10) - Use Alt+C, Alt+F, Alt+L, Alt+D for navigation', 'info');
|
|
}
|
|
}
|
|
|
|
function showMemo() {
|
|
const memoBtn = document.querySelector('[data-action="memo"], .btn-memo');
|
|
if (memoBtn) {
|
|
memoBtn.click();
|
|
} else {
|
|
showToast('Memo function not available on this page', 'info');
|
|
}
|
|
}
|
|
|
|
function toggleTimer() {
|
|
const timerBtn = document.querySelector('[data-action="timer"], .btn-timer');
|
|
if (timerBtn) {
|
|
timerBtn.click();
|
|
} else {
|
|
showToast('Timer function not available on this page', 'info');
|
|
}
|
|
}
|
|
|
|
function showBalanceSummary() {
|
|
const balanceBtn = document.querySelector('[data-action="balance"], .btn-balance');
|
|
if (balanceBtn) {
|
|
balanceBtn.click();
|
|
} else {
|
|
showToast('Balance summary not available on this page', 'info');
|
|
}
|
|
}
|
|
|
|
// Quick creation functions
|
|
function newCustomer() {
|
|
navigateTo('/customers/new');
|
|
}
|
|
|
|
function newFile() {
|
|
navigateTo('/files/new');
|
|
}
|
|
|
|
function newTransaction() {
|
|
navigateTo('/financial/new');
|
|
}
|
|
|
|
// Utility functions
|
|
function isDateField(element) {
|
|
if (!element) return false;
|
|
return element.type === 'date' ||
|
|
element.classList.contains('date-field') ||
|
|
element.getAttribute('data-type') === 'date';
|
|
}
|
|
|
|
function isInTable() {
|
|
const activeElement = document.activeElement;
|
|
return activeElement && (
|
|
activeElement.closest('table') ||
|
|
activeElement.classList.contains('table-row') ||
|
|
activeElement.getAttribute('role') === 'gridcell'
|
|
);
|
|
}
|
|
|
|
function changeDateBy(days) {
|
|
const activeElement = document.activeElement;
|
|
if (isDateField(activeElement)) {
|
|
const currentDate = new Date(activeElement.value || Date.now());
|
|
currentDate.setDate(currentDate.getDate() + days);
|
|
activeElement.value = currentDate.toISOString().split('T')[0];
|
|
activeElement.dispatchEvent(new Event('change', { bubbles: true }));
|
|
}
|
|
}
|
|
|
|
function navigateTable(direction) {
|
|
// Table navigation implementation would depend on the specific table structure
|
|
showToast(`Table navigation: ${direction}`, 'info');
|
|
}
|
|
|
|
function openRecord() {
|
|
const activeElement = document.activeElement;
|
|
const row = activeElement.closest('tr, .table-row');
|
|
if (row) {
|
|
const link = row.querySelector('a, [data-action="open"]');
|
|
if (link) {
|
|
link.click();
|
|
}
|
|
}
|
|
}
|
|
|
|
function showToast(message, type = 'info') {
|
|
// Create toast element
|
|
const toastHtml = `
|
|
<div class="toast align-items-center text-white bg-${type}" role="alert" aria-live="assertive" aria-atomic="true">
|
|
<div class="d-flex">
|
|
<div class="toast-body">${message}</div>
|
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Get or create toast container
|
|
let toastContainer = document.querySelector('.toast-container');
|
|
if (!toastContainer) {
|
|
toastContainer = document.createElement('div');
|
|
toastContainer.className = 'toast-container position-fixed top-0 end-0 p-3';
|
|
document.body.appendChild(toastContainer);
|
|
}
|
|
|
|
// Add toast
|
|
const toastWrapper = document.createElement('div');
|
|
toastWrapper.innerHTML = toastHtml;
|
|
const toastElement = toastWrapper.firstElementChild;
|
|
toastContainer.appendChild(toastElement);
|
|
|
|
// Show toast
|
|
const toast = new bootstrap.Toast(toastElement, { delay: 3000 });
|
|
toast.show();
|
|
|
|
// Remove toast element after it's hidden
|
|
toastElement.addEventListener('hidden.bs.toast', () => {
|
|
toastElement.remove();
|
|
});
|
|
}
|
|
|
|
// Export for use in other scripts
|
|
window.keyboardShortcuts = {
|
|
initialize: initializeKeyboardShortcuts,
|
|
enable: () => { keyboardShortcutsEnabled = true; },
|
|
disable: () => { keyboardShortcutsEnabled = false; },
|
|
showHelp: showHelp
|
|
}; |