maybe good

This commit is contained in:
HotSwapp
2025-08-08 15:55:15 -05:00
parent ab6f163c15
commit b257a06787
80 changed files with 19739 additions and 0 deletions

259
static/css/components.css Normal file
View File

@@ -0,0 +1,259 @@
/* Delphi Database System - Component Styles */
/* Login Component */
.login-page {
background-color: #f8f9fa;
}
.login-card {
max-width: 400px;
margin: 2rem auto;
}
.login-logo {
height: 60px;
margin-bottom: 1rem;
}
.login-form .input-group-text {
background-color: #e9ecef;
border-right: none;
}
.login-form .form-control {
border-left: none;
}
.login-form .form-control:focus {
border-left: none;
box-shadow: none;
}
.login-status {
margin-top: 1rem;
}
/* Customer Management Component */
.customer-search-panel {
background-color: white;
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.customer-table-container {
background-color: white;
border-radius: 0.5rem;
overflow: hidden;
}
.customer-modal .modal-dialog {
max-width: 90%;
}
.customer-form-section {
margin-bottom: 1.5rem;
}
.customer-form-section .card-header {
background-color: #f8f9fa;
border-bottom: 1px solid #dee2e6;
font-weight: 600;
}
.phone-entry {
background-color: #f8f9fa;
padding: 0.75rem;
border-radius: 0.375rem;
margin-bottom: 0.5rem;
}
.phone-entry:last-child {
margin-bottom: 0;
}
/* Statistics Modal */
.stats-modal .modal-body {
background-color: #f8f9fa;
}
.stats-section {
background-color: white;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1rem;
}
/* Navigation Component */
.navbar-shortcuts small {
font-size: 0.7rem;
opacity: 0.8;
}
.keyboard-shortcuts-modal .modal-body {
background-color: #f8f9fa;
}
.shortcuts-section {
background-color: white;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1rem;
}
/* Dashboard Component */
.dashboard-card {
transition: transform 0.2s ease-in-out;
}
.dashboard-card:hover {
transform: translateY(-2px);
}
.dashboard-stats {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 1rem;
}
.recent-activity {
max-height: 400px;
overflow-y: auto;
}
.activity-item {
border-left: 3px solid var(--delphi-primary);
padding-left: 1rem;
margin-bottom: 1rem;
}
.activity-item:last-child {
margin-bottom: 0;
}
/* Form Components */
.form-floating-custom .form-control {
height: calc(3.5rem + 2px);
line-height: 1.25;
}
.form-floating-custom .form-control::placeholder {
color: transparent;
}
.form-floating-custom .form-control:focus ~ .form-label,
.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label {
opacity: 0.65;
transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);
}
/* Search Components */
.search-results {
position: absolute;
top: 100%;
left: 0;
right: 0;
background-color: white;
border: 1px solid #dee2e6;
border-top: none;
border-radius: 0 0 0.375rem 0.375rem;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
z-index: 1000;
max-height: 300px;
overflow-y: auto;
}
.search-result-item {
padding: 0.75rem 1rem;
border-bottom: 1px solid #f8f9fa;
cursor: pointer;
transition: background-color 0.15s ease-in-out;
}
.search-result-item:hover {
background-color: #f8f9fa;
}
.search-result-item:last-child {
border-bottom: none;
}
/* Notification Components */
#notification-container,
.notification-container {
z-index: 1070 !important;
}
#notification-container {
position: fixed;
top: 1rem;
right: 1rem;
width: 300px;
}
.notification {
margin-bottom: 0.5rem;
animation: slideInRight 0.3s ease-out;
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* Loading Components */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.loading-spinner {
width: 2rem;
height: 2rem;
border: 0.25rem solid #f3f3f3;
border-top: 0.25rem solid var(--delphi-primary);
border-radius: 50%;
animation: spin 1s linear infinite;
}
/* Table Components */
.sortable-header {
cursor: pointer;
user-select: none;
}
.sortable-header:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.sortable-header.sort-asc::after {
content: " ↑";
}
.sortable-header.sort-desc::after {
content: " ↓";
}
/* Form Validation */
.invalid-feedback.hidden {
display: none;
}
.invalid-feedback.visible {
display: block;
}

236
static/css/main.css Normal file
View File

@@ -0,0 +1,236 @@
/* Delphi Consulting Group Database System - Main Styles */
/* Variables */
:root {
--delphi-primary: #0d6efd;
--delphi-secondary: #6c757d;
--delphi-success: #198754;
--delphi-info: #0dcaf0;
--delphi-warning: #ffc107;
--delphi-danger: #dc3545;
--delphi-dark: #212529;
}
/* Body and base styles */
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
background-color: #f8f9fa;
}
/* Navigation customizations */
.navbar-brand img {
filter: brightness(0) invert(1);
}
/* Card customizations */
.card {
border: none;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
transition: box-shadow 0.15s ease-in-out;
}
.card:hover {
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
/* Button customizations */
.btn {
border-radius: 0.375rem;
font-weight: 500;
}
.btn-lg small {
font-size: 0.75rem;
font-weight: 400;
}
/* Form customizations */
.form-control {
border-radius: 0.375rem;
}
.form-control:focus {
border-color: var(--delphi-primary);
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
/* Table customizations */
.table {
background-color: white;
}
.table th {
border-top: none;
background-color: var(--delphi-primary);
color: white;
font-weight: 600;
}
.table tbody tr:hover {
background-color: rgba(13, 110, 253, 0.05);
}
/* Keyboard shortcut styling */
kbd {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
color: #495057;
font-size: 0.8rem;
padding: 0.125rem 0.25rem;
}
.nav-link small {
opacity: 0.7;
font-size: 0.7rem;
}
/* Modal customizations */
.modal-content {
border: none;
box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175);
}
.modal-header {
background-color: var(--delphi-primary);
color: white;
}
.modal-header .btn-close {
filter: invert(1);
}
/* Status badges */
.badge {
font-size: 0.8em;
font-weight: 500;
}
/* Utility classes */
.text-primary { color: var(--delphi-primary) !important; }
.text-secondary { color: var(--delphi-secondary) !important; }
.text-success { color: var(--delphi-success) !important; }
.text-info { color: var(--delphi-info) !important; }
.text-warning { color: var(--delphi-warning) !important; }
.text-danger { color: var(--delphi-danger) !important; }
.bg-primary { background-color: var(--delphi-primary) !important; }
.bg-secondary { background-color: var(--delphi-secondary) !important; }
.bg-success { background-color: var(--delphi-success) !important; }
.bg-info { background-color: var(--delphi-info) !important; }
.bg-warning { background-color: var(--delphi-warning) !important; }
.bg-danger { background-color: var(--delphi-danger) !important; }
/* Animation classes */
.fade-in {
animation: fadeIn 0.3s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Responsive adjustments */
@media (max-width: 768px) {
.container-fluid {
padding-left: 1rem;
padding-right: 1rem;
}
.nav-link small {
display: none;
}
.btn-lg small {
display: none;
}
}
/* Loading spinner */
.spinner {
display: inline-block;
width: 1rem;
height: 1rem;
border: 2px solid #f3f3f3;
border-top: 2px solid var(--delphi-primary);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Error and success messages */
.alert {
border: none;
border-radius: 0.5rem;
}
.alert-dismissible .btn-close {
padding: 1rem 0.75rem;
}
/* Data tables */
.table-responsive {
border-radius: 0.5rem;
overflow: hidden;
}
/* Form sections */
.form-section {
background: white;
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.form-section h5 {
color: var(--delphi-primary);
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid #e9ecef;
}
/* Pagination */
.pagination {
margin-bottom: 0;
}
.page-link {
color: var(--delphi-primary);
}
.page-item.active .page-link {
background-color: var(--delphi-primary);
border-color: var(--delphi-primary);
}
/* Visibility utility classes */
.hidden {
display: none !important;
}
.visible {
display: block !important;
}
.visible-inline {
display: inline !important;
}
.visible-inline-block {
display: inline-block !important;
}
/* Customer management specific styles */
.delete-customer-btn {
display: none;
}
.delete-customer-btn.show {
display: inline-block;
}

198
static/css/themes.css Normal file
View File

@@ -0,0 +1,198 @@
/* Delphi Database System - Theme Styles */
/* Light Theme (Default) */
:root {
--delphi-primary: #0d6efd;
--delphi-primary-dark: #0b5ed7;
--delphi-primary-light: #6ea8fe;
--delphi-secondary: #6c757d;
--delphi-success: #198754;
--delphi-info: #0dcaf0;
--delphi-warning: #ffc107;
--delphi-danger: #dc3545;
--delphi-light: #f8f9fa;
--delphi-dark: #212529;
/* Background colors */
--bg-primary: #ffffff;
--bg-secondary: #f8f9fa;
--bg-tertiary: #e9ecef;
/* Text colors */
--text-primary: #212529;
--text-secondary: #6c757d;
--text-muted: #868e96;
/* Border colors */
--border-color: #dee2e6;
--border-light: #f8f9fa;
/* Shadow */
--shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
}
/* Dark Theme */
[data-theme="dark"] {
--delphi-primary: #6ea8fe;
--delphi-primary-dark: #0b5ed7;
--delphi-primary-light: #9ec5fe;
--delphi-secondary: #adb5bd;
--delphi-success: #20c997;
--delphi-info: #39d7f0;
--delphi-warning: #ffcd39;
--delphi-danger: #ea868f;
--delphi-light: #495057;
--delphi-dark: #f8f9fa;
/* Background colors */
--bg-primary: #212529;
--bg-secondary: #343a40;
--bg-tertiary: #495057;
/* Text colors */
--text-primary: #f8f9fa;
--text-secondary: #adb5bd;
--text-muted: #6c757d;
/* Border colors */
--border-color: #495057;
--border-light: #343a40;
/* Shadow */
--shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.25);
--shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.35);
--shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.45);
}
/* High Contrast Theme */
[data-theme="high-contrast"] {
--delphi-primary: #0000ff;
--delphi-primary-dark: #000080;
--delphi-primary-light: #4040ff;
--delphi-secondary: #808080;
--delphi-success: #008000;
--delphi-info: #008080;
--delphi-warning: #ff8000;
--delphi-danger: #ff0000;
--delphi-light: #ffffff;
--delphi-dark: #000000;
/* Background colors */
--bg-primary: #ffffff;
--bg-secondary: #f0f0f0;
--bg-tertiary: #e0e0e0;
/* Text colors */
--text-primary: #000000;
--text-secondary: #404040;
--text-muted: #606060;
/* Border colors */
--border-color: #000000;
--border-light: #808080;
/* Shadow */
--shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.5);
--shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.7);
--shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.8);
}
/* Apply theme variables to components */
body {
background-color: var(--bg-secondary);
color: var(--text-primary);
}
.card {
background-color: var(--bg-primary);
border-color: var(--border-color);
box-shadow: var(--shadow-sm);
}
.navbar-dark {
background-color: var(--delphi-primary) !important;
}
.table {
background-color: var(--bg-primary);
color: var(--text-primary);
}
.table th {
background-color: var(--delphi-primary);
border-color: var(--border-color);
}
.table td {
border-color: var(--border-color);
}
.form-control {
background-color: var(--bg-primary);
border-color: var(--border-color);
color: var(--text-primary);
}
.form-control:focus {
border-color: var(--delphi-primary);
box-shadow: 0 0 0 0.2rem rgba(var(--delphi-primary), 0.25);
}
.modal-content {
background-color: var(--bg-primary);
border-color: var(--border-color);
}
.modal-header {
background-color: var(--delphi-primary);
border-color: var(--border-color);
}
.btn-primary {
background-color: var(--delphi-primary);
border-color: var(--delphi-primary);
}
.btn-primary:hover {
background-color: var(--delphi-primary-dark);
border-color: var(--delphi-primary-dark);
}
.alert {
border-color: var(--border-color);
}
/* Theme transition */
* {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
/* Print styles */
@media print {
:root {
--delphi-primary: #000000;
--bg-primary: #ffffff;
--bg-secondary: #ffffff;
--text-primary: #000000;
--border-color: #000000;
}
.navbar, .btn, .modal, .alert {
display: none !important;
}
.card {
border: 1px solid #000000;
box-shadow: none;
}
}
/* Reduced motion preferences */
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
animation: none !important;
}
}

View File

@@ -0,0 +1,489 @@
/**
* 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
};

409
static/js/main.js Normal file
View File

@@ -0,0 +1,409 @@
/**
* Main JavaScript for Delphi Consulting Group Database System
*/
// Global application state
const app = {
token: localStorage.getItem('auth_token'),
user: null,
initialized: false
};
// Initialize application
document.addEventListener('DOMContentLoaded', function() {
initializeApp();
});
async function initializeApp() {
// Initialize keyboard shortcuts
if (window.keyboardShortcuts) {
window.keyboardShortcuts.initialize();
}
// Initialize tooltips
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
// Initialize popovers
const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
popoverTriggerList.map(function (popoverTriggerEl) {
return new bootstrap.Popover(popoverTriggerEl);
});
// Add form validation classes
initializeFormValidation();
// Initialize API helpers
setupAPIHelpers();
app.initialized = true;
console.log('Delphi Database System initialized');
}
// Form validation
function initializeFormValidation() {
// Add Bootstrap validation styles
const forms = document.querySelectorAll('form.needs-validation');
forms.forEach(form => {
form.addEventListener('submit', function(event) {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add('was-validated');
});
});
// Real-time validation for specific fields
const requiredFields = document.querySelectorAll('input[required], select[required], textarea[required]');
requiredFields.forEach(field => {
field.addEventListener('blur', function() {
validateField(field);
});
});
}
function validateField(field) {
const isValid = field.checkValidity();
field.classList.remove('is-valid', 'is-invalid');
field.classList.add(isValid ? 'is-valid' : 'is-invalid');
// Show/hide custom feedback
const feedback = field.parentNode.querySelector('.invalid-feedback');
if (feedback) {
feedback.classList.toggle('hidden', isValid);
feedback.classList.toggle('visible', !isValid);
}
}
// API helpers
function setupAPIHelpers() {
// Set up default headers for all API calls
window.apiHeaders = {
'Content-Type': 'application/json',
'Accept': 'application/json'
};
if (app.token) {
window.apiHeaders['Authorization'] = `Bearer ${app.token}`;
}
}
// API utility functions
async function apiCall(url, options = {}) {
const config = {
headers: { ...window.apiHeaders, ...options.headers },
...options
};
try {
const response = await fetch(url, config);
if (response.status === 401) {
// Token expired or invalid
logout();
throw new Error('Authentication required');
}
if (!response.ok) {
const errorData = await response.json().catch(() => ({ detail: 'Request failed' }));
throw new Error(errorData.detail || `HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API call failed:', error);
showNotification(`Error: ${error.message}`, 'error');
throw error;
}
}
async function apiGet(url) {
return apiCall(url, { method: 'GET' });
}
async function apiPost(url, data) {
return apiCall(url, {
method: 'POST',
body: JSON.stringify(data)
});
}
async function apiPut(url, data) {
return apiCall(url, {
method: 'PUT',
body: JSON.stringify(data)
});
}
async function apiDelete(url) {
return apiCall(url, { method: 'DELETE' });
}
// Authentication functions
function setAuthToken(token) {
app.token = token;
localStorage.setItem('auth_token', token);
window.apiHeaders['Authorization'] = `Bearer ${token}`;
}
function logout() {
app.token = null;
app.user = null;
localStorage.removeItem('auth_token');
delete window.apiHeaders['Authorization'];
window.location.href = '/login';
}
// Notification system
function showNotification(message, type = 'info', duration = 5000) {
const notificationContainer = getOrCreateNotificationContainer();
const notification = document.createElement('div');
notification.className = `alert alert-${type} alert-dismissible fade show`;
notification.setAttribute('role', 'alert');
notification.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
`;
notificationContainer.appendChild(notification);
// Auto-dismiss after duration
if (duration > 0) {
setTimeout(() => {
notification.remove();
}, duration);
}
return notification;
}
function getOrCreateNotificationContainer() {
let container = document.querySelector('#notification-container');
if (!container) {
container = document.createElement('div');
container.id = 'notification-container';
container.className = 'position-fixed top-0 end-0 p-3';
container.classList.add('notification-container');
document.body.appendChild(container);
}
return container;
}
// Loading states
function showLoading(element, text = 'Loading...') {
const spinner = `<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>`;
const originalContent = element.innerHTML;
element.innerHTML = `${spinner}${text}`;
element.disabled = true;
element.dataset.originalContent = originalContent;
}
function hideLoading(element) {
if (element.dataset.originalContent) {
element.innerHTML = element.dataset.originalContent;
delete element.dataset.originalContent;
}
element.disabled = false;
}
// Table helpers
function initializeDataTable(tableId, options = {}) {
const table = document.getElementById(tableId);
if (!table) return null;
// Add sorting capability
const headers = table.querySelectorAll('th[data-sort]');
headers.forEach(header => {
header.classList.add('sortable-header');
header.addEventListener('click', () => sortTable(table, header));
});
// Add row selection if enabled
if (options.selectable) {
addRowSelection(table);
}
return table;
}
function sortTable(table, header) {
const columnIndex = Array.from(header.parentNode.children).indexOf(header);
const sortType = header.dataset.sort;
const tbody = table.querySelector('tbody');
const rows = Array.from(tbody.querySelectorAll('tr'));
const isAscending = !header.classList.contains('sort-asc');
// Remove sort classes from all headers
table.querySelectorAll('th').forEach(th => {
th.classList.remove('sort-asc', 'sort-desc');
});
// Add sort class to current header
header.classList.add(isAscending ? 'sort-asc' : 'sort-desc');
rows.sort((a, b) => {
const aValue = a.children[columnIndex].textContent.trim();
const bValue = b.children[columnIndex].textContent.trim();
let comparison = 0;
if (sortType === 'number') {
comparison = parseFloat(aValue) - parseFloat(bValue);
} else if (sortType === 'date') {
comparison = new Date(aValue) - new Date(bValue);
} else {
comparison = aValue.localeCompare(bValue);
}
return isAscending ? comparison : -comparison;
});
// Re-append sorted rows
rows.forEach(row => tbody.appendChild(row));
}
function addRowSelection(table) {
const tbody = table.querySelector('tbody');
tbody.addEventListener('click', function(e) {
const row = e.target.closest('tr');
if (row && e.target.type !== 'checkbox') {
row.classList.toggle('table-active');
// Trigger custom event
const event = new CustomEvent('rowSelect', {
detail: { row, selected: row.classList.contains('table-active') }
});
table.dispatchEvent(event);
}
});
}
// Form helpers
function serializeForm(form) {
const formData = new FormData(form);
const data = {};
for (let [key, value] of formData.entries()) {
// Handle multiple values (checkboxes, multi-select)
if (data.hasOwnProperty(key)) {
if (!Array.isArray(data[key])) {
data[key] = [data[key]];
}
data[key].push(value);
} else {
data[key] = value;
}
}
return data;
}
function populateForm(form, data) {
Object.keys(data).forEach(key => {
const field = form.querySelector(`[name="${key}"]`);
if (field) {
if (field.type === 'checkbox' || field.type === 'radio') {
field.checked = data[key];
} else {
field.value = data[key];
}
}
});
}
// Search functionality
function initializeSearch(searchInput, resultsContainer, searchFunction) {
let searchTimeout;
searchInput.addEventListener('input', function() {
clearTimeout(searchTimeout);
const query = this.value.trim();
if (query.length < 2) {
resultsContainer.innerHTML = '';
return;
}
searchTimeout = setTimeout(async () => {
try {
showLoading(resultsContainer, 'Searching...');
const results = await searchFunction(query);
displaySearchResults(resultsContainer, results);
} catch (error) {
resultsContainer.innerHTML = '<p class="text-danger">Search failed</p>';
}
}, 300);
});
}
function displaySearchResults(container, results) {
if (!results || results.length === 0) {
container.innerHTML = '<p class="text-muted">No results found</p>';
return;
}
const resultsHtml = results.map(result => `
<div class="search-result p-2 border-bottom">
<div class="d-flex justify-content-between">
<div>
<strong>${result.title}</strong>
<small class="text-muted d-block">${result.description}</small>
</div>
<span class="badge bg-secondary">${result.type}</span>
</div>
</div>
`).join('');
container.innerHTML = resultsHtml;
}
// Utility functions
function formatCurrency(amount) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount);
}
function formatDate(date) {
return new Intl.DateTimeFormat('en-US').format(new Date(date));
}
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
// Export global functions
window.app = app;
window.showNotification = showNotification;
window.apiGet = apiGet;
window.apiPost = apiPost;
window.apiPut = apiPut;
window.apiDelete = apiDelete;
window.formatCurrency = formatCurrency;
window.formatDate = formatDate;