This commit is contained in:
HotSwapp
2025-08-09 16:37:57 -05:00
parent 5f74243c8c
commit c2f3c4411d
35 changed files with 9209 additions and 4633 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -5,135 +5,146 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{{ title }}{% endblock %}</title>
<!-- Bootstrap 5.3 CDN -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
<!-- Icons (Font Awesome) -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<!-- Tailwind CSS -->
<!-- Custom Tailwind CSS -->
<link href="/static/css/tailwind.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="/static/css/main.css" rel="stylesheet">
<link href="/static/css/themes.css" rel="stylesheet">
<link href="/static/css/components.css" rel="stylesheet">
{% block bridge_css %}{% endblock %}
<style>
/* Footer Enhancements */
footer .btn-outline-primary:hover {
background-color: #0d6efd;
border-color: #0d6efd;
color: white;
transform: translateY(-1px);
transition: all 0.2s ease;
}
footer .text-primary:hover {
color: #0056b3 !important;
transition: color 0.2s ease;
}
footer small {
color: #6c757d !important;
}
#currentPageDisplay {
color: #495057 !important;
font-weight: 500;
}
/* Responsive footer adjustments */
@media (max-width: 768px) {
footer .row {
text-align: center !important;
}
footer .col-md-6:first-child {
margin-bottom: 0.5rem;
}
}
</style>
{% block extra_head %}{% endblock %}
</head>
<body class="d-flex flex-column min-vh-100">
<body class="flex flex-col min-h-screen bg-neutral-50 dark:bg-neutral-900 text-neutral-900 dark:text-neutral-50 antialiased">
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">
Delphi Database System
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="/customers" data-shortcut="Alt+C">
<i class="bi bi-people"></i> Customers <small>(Alt+C)</small>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/files" data-shortcut="Alt+F">
<i class="bi bi-folder"></i> Files <small>(Alt+F)</small>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/financial" data-shortcut="Alt+L">
<i class="bi bi-calculator"></i> Ledger <small>(Alt+L)</small>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/documents" data-shortcut="Alt+D">
<i class="bi bi-file-text"></i> Documents <small>(Alt+D)</small>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/search" data-shortcut="Ctrl+F">
<i class="bi bi-search"></i> Search <small>(Ctrl+F)</small>
</a>
</li>
</ul>
<nav class="bg-primary-600 border-b border-primary-700 shadow-sm">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<!-- Brand -->
<div class="flex items-center">
<a href="/" class="text-white font-semibold text-xl hover:text-primary-100 transition-colors">
Delphi Database System
</a>
</div>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="bi bi-person-circle"></i> User
</a>
<ul class="dropdown-menu">
<li id="admin-menu-item" style="display: none;"><a class="dropdown-item" href="/admin" data-shortcut="Alt+A"><i class="bi bi-gear"></i> Admin <small>(Alt+A)</small></a></li>
<li id="admin-menu-divider" style="display: none;"><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="logout()"><i class="bi bi-box-arrow-right"></i> Logout</a></li>
</ul>
</li>
</ul>
<!-- Desktop Navigation -->
<div class="hidden md:flex items-center space-x-1">
<a href="/customers" data-shortcut="Alt+C" class="flex items-center gap-2 px-3 py-2 rounded-lg text-primary-100 hover:text-white hover:bg-primary-700 transition-all duration-200">
<i class="fa-solid fa-users"></i>
<span>Customers</span>
</a>
<a href="/files" data-shortcut="Alt+F" class="flex items-center gap-2 px-3 py-2 rounded-lg text-primary-100 hover:text-white hover:bg-primary-700 transition-all duration-200">
<i class="fa-solid fa-folder"></i>
<span>Files</span>
</a>
<a href="/financial" data-shortcut="Alt+L" class="flex items-center gap-2 px-3 py-2 rounded-lg text-primary-100 hover:text-white hover:bg-primary-700 transition-all duration-200">
<i class="fa-solid fa-calculator"></i>
<span>Ledger</span>
</a>
<a href="/documents" data-shortcut="Alt+D" class="flex items-center gap-2 px-3 py-2 rounded-lg text-primary-100 hover:text-white hover:bg-primary-700 transition-all duration-200">
<i class="fa-solid fa-file-lines"></i>
<span>Documents</span>
</a>
<a href="/search" data-shortcut="Ctrl+F" class="flex items-center gap-2 px-3 py-2 rounded-lg text-primary-100 hover:text-white hover:bg-primary-700 transition-all duration-200">
<i class="fa-solid fa-magnifying-glass"></i>
<span>Search</span>
</a>
</div>
<!-- Right side items -->
<div class="flex items-center space-x-3">
<!-- Theme Toggle -->
<button onclick="toggleTheme()" title="Toggle dark mode" class="flex items-center justify-center w-10 h-10 bg-primary-700 hover:bg-primary-800 text-white rounded-lg transition-colors duration-200">
<i class="fas fa-sun dark:hidden text-sm"></i>
<i class="fas fa-moon hidden dark:block text-sm"></i>
</button>
<!-- User Dropdown -->
<div class="relative" id="userDropdown">
<button onclick="toggleUserMenu()" class="flex items-center gap-2 px-3 py-2 rounded-lg text-primary-100 hover:text-white hover:bg-primary-700 transition-all duration-200">
<i class="fa-solid fa-circle-user"></i>
<span>User</span>
<i class="fa-solid fa-chevron-down text-xs"></i>
</button>
<div id="userMenu" class="hidden absolute right-0 mt-2 w-48 bg-white dark:bg-neutral-800 rounded-lg shadow-lg border border-neutral-200 dark:border-neutral-700 z-50">
<div class="py-1">
<a id="admin-menu-item" href="/admin" class="hidden flex items-center gap-2 px-4 py-2 text-sm text-neutral-700 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-700 transition-colors">
<i class="fa-solid fa-gear"></i>
<span>Admin</span>
</a>
<div id="admin-menu-divider" class="hidden border-t border-neutral-200 dark:border-neutral-700 my-1"></div>
<a href="#" onclick="logout()" class="flex items-center gap-2 px-4 py-2 text-sm text-neutral-700 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-700 transition-colors">
<i class="fa-solid fa-right-from-bracket"></i>
<span>Logout</span>
</a>
</div>
</div>
</div>
<!-- Mobile menu button -->
<button onclick="toggleMobileMenu()" class="md:hidden flex items-center justify-center w-10 h-10 bg-primary-700 hover:bg-primary-800 text-white rounded-lg transition-colors">
<i id="mobileMenuIcon" class="fa-solid fa-bars"></i>
</button>
</div>
</div>
<!-- Mobile Navigation -->
<div id="mobileMenu" class="hidden md:hidden border-t border-primary-700 pt-4 pb-4">
<div class="space-y-1">
<a href="/customers" class="flex items-center gap-3 px-3 py-2 rounded-lg text-primary-100 hover:text-white hover:bg-primary-700 transition-all duration-200">
<i class="fa-solid fa-users"></i>
<span>Customers</span>
</a>
<a href="/files" class="flex items-center gap-3 px-3 py-2 rounded-lg text-primary-100 hover:text-white hover:bg-primary-700 transition-all duration-200">
<i class="fa-solid fa-folder"></i>
<span>Files</span>
</a>
<a href="/financial" class="flex items-center gap-3 px-3 py-2 rounded-lg text-primary-100 hover:text-white hover:bg-primary-700 transition-all duration-200">
<i class="fa-solid fa-calculator"></i>
<span>Ledger</span>
</a>
<a href="/documents" class="flex items-center gap-3 px-3 py-2 rounded-lg text-primary-100 hover:text-white hover:bg-primary-700 transition-all duration-200">
<i class="fa-solid fa-file-lines"></i>
<span>Documents</span>
</a>
<a href="/search" class="flex items-center gap-3 px-3 py-2 rounded-lg text-primary-100 hover:text-white hover:bg-primary-700 transition-all duration-200">
<i class="fa-solid fa-magnifying-glass"></i>
<span>Search</span>
</a>
</div>
</div>
</div>
</nav>
<!-- Main Content -->
<main class="flex-grow-1">
<div class="container-fluid mt-3 mb-4">
<main class="flex-grow">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
{% block content %}{% endblock %}
</div>
</main>
<!-- Footer -->
<footer class="mt-auto py-3 border-top shadow-sm" style="background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-color: #dee2e6 !important;">
<div class="container">
<div class="row align-items-center">
<div class="col-md-6">
<small class="text-muted">
&copy; <span id="currentYear"></span> Delphi Consulting Group Database System
<span class="mx-2">|</span>
<span id="currentPageDisplay">Loading...</span>
</small>
<footer class="mt-auto border-t border-neutral-200 dark:border-neutral-700 bg-white dark:bg-neutral-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div class="flex flex-col sm:flex-row items-center justify-between gap-4">
<div class="text-sm text-neutral-500 dark:text-neutral-400">
&copy; <span id="currentYear"></span> Delphi Consulting Group Database System
<span class="mx-2">|</span>
<span id="currentPageDisplay">Loading...</span>
</div>
<div class="col-md-6 text-end">
<button type="button" class="btn btn-outline-primary btn-sm me-3" onclick="openSupportModal()">
<i class="fas fa-bug me-1"></i>Report Issue
<div class="flex items-center gap-4">
<button type="button" onclick="openSupportModal()" class="bg-primary-600 text-white hover:bg-primary-700 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors duration-200 flex items-center gap-2">
<i class="fas fa-bug"></i>
<span>Report Issue</span>
</button>
<small class="text-muted">
Found a bug? <a href="#" onclick="openSupportModal()" class="text-primary text-decoration-none">Report Issue</a>
</small>
<div class="text-sm text-neutral-500 dark:text-neutral-400">
Found a bug?
<button onclick="openSupportModal()" class="text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300 transition-colors duration-200 underline">
Report Issue
</button>
</div>
</div>
</div>
</div>
@@ -143,71 +154,220 @@
{% include 'support_modal.html' %}
<!-- Keyboard Shortcuts Help Modal -->
<div class="modal fade" id="shortcutsModal" tabindex="-1" aria-labelledby="shortcutsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="shortcutsModalLabel">
<i class="bi bi-keyboard"></i> Keyboard Shortcuts
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6">
<h6><i class="bi bi-house"></i> Navigation</h6>
<ul class="list-unstyled">
<li><kbd>Alt+C</kbd> - Customers/Rolodex</li>
<li><kbd>Alt+F</kbd> - File Cabinet</li>
<li><kbd>Alt+L</kbd> - Ledger/Financial</li>
<li><kbd>Alt+D</kbd> - Documents/QDROs</li>
<li><kbd>Alt+A</kbd> - Admin Panel</li>
<li><kbd>Ctrl+F</kbd> - Global Search</li>
</ul>
<h6><i class="bi bi-pencil"></i> Forms</h6>
<ul class="list-unstyled">
<li><kbd>Ctrl+N</kbd> - New Record</li>
<li><kbd>Ctrl+S</kbd> - Save</li>
<li><kbd>F9</kbd> - Edit Mode</li>
<li><kbd>F2</kbd> - Complete/Save</li>
<li><kbd>F8</kbd> - Clear/Cancel</li>
<li><kbd>Del</kbd> - Delete Record</li>
<li><kbd>Esc</kbd> - Cancel/Close</li>
<div id="shortcutsModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div class="bg-white dark:bg-neutral-800 rounded-xl shadow-xl max-w-4xl w-full max-h-screen overflow-hidden">
<div class="flex items-center justify-between px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
<h2 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 flex items-center gap-2">
<i class="fa-solid fa-keyboard"></i>
<span>Keyboard Shortcuts</span>
</h2>
<button onclick="closeShortcutsModal()" class="text-neutral-400 hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-300 transition-colors">
<i class="fa-solid fa-xmark text-xl"></i>
</button>
</div>
<div class="px-6 py-4 max-h-96 overflow-y-auto scrollbar-thin">
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="space-y-6">
<div>
<h3 class="flex items-center gap-2 text-sm font-semibold text-neutral-700 dark:text-neutral-300 mb-3">
<i class="fa-solid fa-house"></i>
<span>Navigation</span>
</h3>
<ul class="space-y-2 text-sm">
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Customers/Rolodex</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">Alt+C</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">File Cabinet</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">Alt+F</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Ledger/Financial</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">Alt+L</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Documents/QDROs</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">Alt+D</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Admin Panel</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">Alt+A</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Global Search</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">Ctrl+F</kbd>
</li>
</ul>
</div>
<div class="col-md-6">
<h6><i class="bi bi-list"></i> Lists/Tables</h6>
<ul class="list-unstyled">
<li><kbd>↑/↓</kbd> - Navigate records</li>
<li><kbd>Page Up/Down</kbd> - Page navigation</li>
<li><kbd>Home/End</kbd> - First/Last record</li>
<li><kbd>Enter</kbd> - Open/Edit record</li>
<li><kbd>+/-</kbd> - Change dates</li>
<div>
<h3 class="flex items-center gap-2 text-sm font-semibold text-neutral-700 dark:text-neutral-300 mb-3">
<i class="fa-solid fa-pencil"></i>
<span>Forms</span>
</h3>
<ul class="space-y-2 text-sm">
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">New Record</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">Ctrl+N</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Save</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">Ctrl+S</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Edit Mode</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">F9</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Complete/Save</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">F2</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Clear/Cancel</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">F8</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Delete Record</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">Del</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Cancel/Close</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">Esc</kbd>
</li>
</ul>
<h6><i class="bi bi-tools"></i> System</h6>
<ul class="list-unstyled">
<li><kbd>F1</kbd> - Help (this dialog)</li>
<li><kbd>F10</kbd> - Menu</li>
<li><kbd>Alt+M</kbd> - Memo/Notes</li>
<li><kbd>Alt+T</kbd> - Time Tracker</li>
<li><kbd>Alt+B</kbd> - Balance Summary</li>
</div>
</div>
<div class="space-y-6">
<div>
<h3 class="flex items-center gap-2 text-sm font-semibold text-neutral-700 dark:text-neutral-300 mb-3">
<i class="fa-solid fa-list"></i>
<span>Lists/Tables</span>
</h3>
<ul class="space-y-2 text-sm">
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Navigate records</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">↑/↓</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Page navigation</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">Page Up/Down</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">First/Last record</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">Home/End</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Open/Edit record</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">Enter</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Change dates</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">+/-</kbd>
</li>
</ul>
</div>
<div>
<h3 class="flex items-center gap-2 text-sm font-semibold text-neutral-700 dark:text-neutral-300 mb-3">
<i class="fa-solid fa-screwdriver-wrench"></i>
<span>System</span>
</h3>
<ul class="space-y-2 text-sm">
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Help (this dialog)</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">F1</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Menu</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">F10</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Memo/Notes</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">Alt+M</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Time Tracker</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">Alt+T</kbd>
</li>
<li class="flex items-center justify-between">
<span class="text-neutral-600 dark:text-neutral-400">Balance Summary</span>
<kbd class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 rounded text-xs font-mono">Alt+B</kbd>
</li>
</ul>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
<div class="flex items-center justify-end gap-3 px-6 py-4 border-t border-neutral-200 dark:border-neutral-700 bg-neutral-50 dark:bg-neutral-800/50">
<button onclick="closeShortcutsModal()" class="px-4 py-2 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-200 dark:hover:bg-neutral-600 rounded-lg transition-colors duration-200">
Close
</button>
</div>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Custom Navigation JS -->
<script>
function toggleUserMenu() {
const menu = document.getElementById('userMenu');
menu.classList.toggle('hidden');
}
function toggleMobileMenu() {
const menu = document.getElementById('mobileMenu');
const icon = document.getElementById('mobileMenuIcon');
menu.classList.toggle('hidden');
icon.classList.toggle('fa-bars');
icon.classList.toggle('fa-xmark');
}
function openShortcutsModal() {
document.getElementById('shortcutsModal').classList.remove('hidden');
}
function closeShortcutsModal() {
document.getElementById('shortcutsModal').classList.add('hidden');
}
// Close menus when clicking outside
document.addEventListener('click', function(event) {
const dropdown = document.getElementById('userDropdown');
const menu = document.getElementById('userMenu');
const shortcutsModal = document.getElementById('shortcutsModal');
if (!dropdown.contains(event.target)) {
menu.classList.add('hidden');
}
// Close shortcuts modal when clicking outside
if (event.target === shortcutsModal) {
closeShortcutsModal();
}
});
// Handle escape key for modal
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeShortcutsModal();
}
});
</script>
<script>
// Global modal helpers for Tailwind-based modals
function openModal(id) {
const el = document.getElementById(id);
if (el) el.classList.remove('hidden');
}
function closeModal(id) {
const el = document.getElementById(id);
if (el) el.classList.add('hidden');
}
</script>
<!-- Custom JavaScript -->
<script src="/static/js/alerts.js"></script>
<script src="/static/js/keyboard-shortcuts.js"></script>
<script src="/static/js/main.js"></script>
@@ -272,14 +432,14 @@
const user = await response.json();
if (user.is_admin) {
// Show admin menu items
document.getElementById('admin-menu-item').style.display = 'block';
document.getElementById('admin-menu-divider').style.display = 'block';
document.getElementById('admin-menu-item').classList.remove('hidden');
document.getElementById('admin-menu-divider').classList.remove('hidden');
}
// Update user display name if available
const userDropdown = document.querySelector('.nav-link.dropdown-toggle');
const userDropdown = document.querySelector('#userDropdown button span');
if (user.full_name && userDropdown) {
userDropdown.innerHTML = `<i class="bi bi-person-circle"></i> ${user.full_name}`;
userDropdown.textContent = user.full_name;
}
}
} catch (error) {
@@ -368,28 +528,136 @@
}
}
function setupActivityMonitoring() {
async function setupActivityMonitoring() {
let lastActivity = Date.now();
let warningShown = false;
let inactivityWarningMinutes = 240; // default 4 hours
const inactivityGraceMinutes = 5; // auto-logout after warning + 5 minutes
let inactivityAlertEl = null;
try {
const minutes = await getInactivityWarningMinutes();
if (Number.isFinite(minutes) && minutes > 0) {
inactivityWarningMinutes = minutes;
}
} catch (e) {
console.debug('Using default inactivity warning minutes');
}
// Track user activity
const activityEvents = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart'];
activityEvents.forEach(event => {
document.addEventListener(event, () => {
lastActivity = Date.now();
warningShown = false; // Reset warning flag on activity
hideInactivityWarning();
});
});
// Check every 30 minutes if user has been inactive for more than 4 hours
function showInactivityWarning() {
hideInactivityWarning();
const msg = `You've been inactive. Your session may expire due to inactivity.`;
if (window.alerts && typeof window.alerts.show === 'function') {
inactivityAlertEl = window.alerts.show(msg, 'warning', {
title: 'Session Warning',
html: false,
duration: 0,
dismissible: true,
id: 'inactivity-warning',
actions: [
{
label: 'Stay Logged In',
classes: 'bg-warning-600 hover:bg-warning-700 text-white text-xs px-3 py-1 rounded',
onClick: () => extendSession(),
autoClose: true
},
{
label: 'Dismiss',
classes: 'bg-neutral-200 hover:bg-neutral-300 text-neutral-800 dark:bg-neutral-700 dark:hover:bg-neutral-600 dark:text-neutral-200 text-xs px-3 py-1 rounded',
onClick: () => hideInactivityWarning(),
autoClose: true
}
]
});
} else {
// Fallback
alert('Session Warning: ' + msg);
}
// Auto-hide after 2 minutes if no action taken
setTimeout(() => {
hideInactivityWarning();
}, 2 * 60 * 1000);
}
function hideInactivityWarning() {
const el = document.getElementById('inactivity-warning');
if (el && el.remove) el.remove();
inactivityAlertEl = null;
}
function extendSession() {
refreshTokenIfNeeded();
hideInactivityWarning();
showSessionExtendedNotification();
}
// Check every 5 minutes for inactivity
setInterval(() => {
const now = Date.now();
const fourHours = 4 * 60 * 60 * 1000;
if (now - lastActivity > fourHours) {
// User has been inactive for 4+ hours, logout
const warningMs = inactivityWarningMinutes * 60 * 1000;
const logoutMs = (inactivityWarningMinutes + inactivityGraceMinutes) * 60 * 1000;
const timeSinceActivity = now - lastActivity;
if (timeSinceActivity > warningMs && !warningShown) {
showInactivityWarning();
warningShown = true;
}
if (timeSinceActivity > logoutMs) {
logout('Session expired due to inactivity');
}
}, 30 * 60 * 1000); // Check every 30 minutes
}, 5 * 60 * 1000); // Check every 5 minutes
}
async function getInactivityWarningMinutes() {
const token = localStorage.getItem('auth_token');
if (!token) return 240;
const resp = await fetch('/api/settings/inactivity_warning_minutes', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (!resp.ok) return 240;
const data = await resp.json();
if (typeof data.minutes === 'number') return data.minutes;
const parsed = parseInt(data.setting_value || data.minutes, 10);
return Number.isFinite(parsed) ? parsed : 240;
}
function showSessionExtendedNotification() {
if (window.alerts && typeof window.alerts.success === 'function') {
window.alerts.success('Your session has been refreshed successfully.', {
title: 'Session Extended',
duration: 3000
});
return;
}
// Fallback
const notification = document.createElement('div');
notification.className = 'fixed top-4 right-4 bg-green-100 border-l-4 border-green-500 text-green-700 p-4 rounded-lg shadow-lg z-50 max-w-sm';
notification.innerHTML = `
<div class="flex items-center">
<div class="flex-shrink-0">
<i class="fas fa-check-circle text-green-500"></i>
</div>
<div class="ml-3">
<p class="text-sm font-medium">Session Extended</p>
<p class="text-xs mt-1">Your session has been refreshed successfully.</p>
</div>
</div>
`;
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 3000);
}
// Enhanced logout function
@@ -402,12 +670,101 @@
window.location.href = '/login';
}
// Theme Management
function toggleTheme() {
const html = document.documentElement;
const isDark = html.classList.contains('dark');
if (isDark) {
html.classList.remove('dark');
localStorage.setItem('theme-preference', 'light');
saveThemePreference('light');
} else {
html.classList.add('dark');
localStorage.setItem('theme-preference', 'dark');
saveThemePreference('dark');
}
}
function initializeTheme() {
// Check for saved theme preference
const savedTheme = localStorage.getItem('theme-preference');
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
// Use saved theme, or default to system preference
const theme = savedTheme || (prefersDark ? 'dark' : 'light');
if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
// Load user's theme preference from server if authenticated
loadUserThemePreference();
}
async function saveThemePreference(theme) {
const token = localStorage.getItem('auth_token');
if (!token || isLoginPage()) return;
try {
await fetch('/api/auth/theme-preference', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ theme_preference: theme })
});
} catch (error) {
console.log('Could not save theme preference to server:', error.message);
}
}
async function loadUserThemePreference() {
const token = localStorage.getItem('auth_token');
if (!token || isLoginPage()) return;
try {
const response = await fetch('/api/auth/me', {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const user = await response.json();
if (user.theme_preference) {
if (user.theme_preference === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
localStorage.setItem('theme-preference', user.theme_preference);
}
}
} catch (error) {
console.log('Could not load theme preference from server:', error.message);
}
}
// Initialize theme before other scripts
initializeTheme();
// Make functions globally available
window.authManager = {
checkTokenValidity,
refreshTokenIfNeeded,
logout
};
window.themeManager = {
toggleTheme,
initializeTheme,
saveThemePreference,
loadUserThemePreference
};
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -1,174 +1,184 @@
{% extends "base.html" %}
{% block title %}Dashboard - {{ super() }}{% endblock %}
{% block title %}Dashboard - Delphi Database{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1><i class="bi bi-speedometer2"></i> Dashboard</h1>
<div>
<button class="btn btn-outline-secondary btn-sm" onclick="showShortcuts()">
<i class="bi bi-keyboard"></i> Shortcuts (F1)
</button>
<div class="space-y-6">
<!-- Page Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div class="flex items-center gap-3">
<div class="flex items-center justify-center w-10 h-10 bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 rounded-xl">
<i class="fa-solid fa-gauge-high text-lg"></i>
</div>
<h1 class="text-2xl font-bold text-neutral-900 dark:text-neutral-100">Dashboard</h1>
</div>
<div class="flex items-center gap-3">
<button onclick="openShortcutsModal()" class="bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-200 dark:hover:bg-neutral-600 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors duration-200 flex items-center gap-2">
<i class="fa-solid fa-keyboard"></i>
<span>Shortcuts (F1)</span>
</button>
</div>
</div>
</div>
<!-- Quick Stats Cards -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card bg-primary text-white">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="me-3">
<i class="bi bi-people fs-1"></i>
</div>
<div>
<h5 class="card-title">Customers</h5>
<h2 class="mb-0" id="customer-count">-</h2>
</div>
<!-- Quick Stats Cards -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div class="bg-primary-600 text-white rounded-xl shadow-soft p-4">
<div class="flex items-center gap-3">
<div class="flex items-center justify-center w-10 h-10 bg-primary-700 rounded-lg">
<i class="fa-solid fa-users text-xl"></i>
</div>
<div>
<h5 class="text-sm font-medium mb-1">Customers</h5>
<h2 class="text-2xl font-bold mb-0" id="customer-count">-</h2>
</div>
<a href="/customers" class="text-white-50 small">
View all <i class="bi bi-arrow-right"></i>
</a>
</div>
<a href="/customers" class="text-primary-200 hover:text-white text-xs font-medium flex items-center gap-1 mt-2 transition-colors">
View all
<i class="fa-solid fa-arrow-right"></i>
</a>
</div>
<div class="bg-success-600 text-white rounded-xl shadow-soft p-4">
<div class="flex items-center gap-3">
<div class="flex items-center justify-center w-10 h-10 bg-success-700 rounded-lg">
<i class="fa-solid fa-folder text-xl"></i>
</div>
<div>
<h5 class="text-sm font-medium mb-1">Active Files</h5>
<h2 class="text-2xl font-bold mb-0" id="file-count">-</h2>
</div>
</div>
<a href="/files" class="text-success-200 hover:text-white text-xs font-medium flex items-center gap-1 mt-2 transition-colors">
View all
<i class="fa-solid fa-arrow-right"></i>
</a>
</div>
<div class="bg-info-600 text-white rounded-xl shadow-soft p-4">
<div class="flex items-center gap-3">
<div class="flex items-center justify-center w-10 h-10 bg-info-700 rounded-lg">
<i class="fa-solid fa-receipt text-xl"></i>
</div>
<div>
<h5 class="text-sm font-medium mb-1">Transactions</h5>
<h2 class="text-2xl font-bold mb-0" id="transaction-count">-</h2>
</div>
</div>
<a href="/financial" class="text-info-200 hover:text-white text-xs font-medium flex items-center gap-1 mt-2 transition-colors">
View ledger
<i class="fa-solid fa-arrow-right"></i>
</a>
</div>
<div class="bg-warning-600 text-white rounded-xl shadow-soft p-4">
<div class="flex items-center gap-3">
<div class="flex items-center justify-center w-10 h-10 bg-warning-700 rounded-lg">
<i class="fa-regular fa-file-lines text-xl"></i>
</div>
<div>
<h5 class="text-sm font-medium mb-1">Documents</h5>
<h2 class="text-2xl font-bold mb-0" id="document-count">-</h2>
</div>
</div>
<a href="/documents" class="text-warning-200 hover:text-white text-xs font-medium flex items-center gap-1 mt-2 transition-colors">
View all
<i class="fa-solid fa-arrow-right"></i>
</a>
</div>
</div>
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="me-3">
<i class="bi bi-folder fs-1"></i>
</div>
<div>
<h5 class="card-title">Active Files</h5>
<h2 class="mb-0" id="file-count">-</h2>
</div>
</div>
<a href="/files" class="text-white-50 small">
View all <i class="bi bi-arrow-right"></i>
</a>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="me-3">
<i class="bi bi-receipt fs-1"></i>
</div>
<div>
<h5 class="card-title">Transactions</h5>
<h2 class="mb-0" id="transaction-count">-</h2>
</div>
</div>
<a href="/financial" class="text-white-50 small">
View ledger <i class="bi bi-arrow-right"></i>
</a>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning text-dark">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="me-3">
<i class="bi bi-file-text fs-1"></i>
</div>
<div>
<h5 class="card-title">Documents</h5>
<h2 class="mb-0" id="document-count">-</h2>
</div>
</div>
<a href="/documents" class="text-dark-50 small">
View all <i class="bi bi-arrow-right"></i>
</a>
</div>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="row mb-4">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5><i class="bi bi-lightning"></i> Quick Actions</h5>
<!-- Quick Actions and Recent Activity -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="md:col-span-2 bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-soft">
<div class="px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
<h5 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 flex items-center gap-2">
<i class="fa-solid fa-bolt"></i>
<span>Quick Actions</span>
</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6 mb-3">
<div class="d-grid gap-2">
<button class="btn btn-outline-primary btn-lg" onclick="newCustomer()">
<i class="bi bi-person-plus"></i> New Customer
<small class="d-block text-muted">Ctrl+Shift+C</small>
</button>
<button class="btn btn-outline-success btn-lg" onclick="newFile()">
<i class="bi bi-folder-plus"></i> New File
<small class="d-block text-muted">Ctrl+Shift+F</small>
</button>
</div>
<div class="p-6">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-3">
<button onclick="newCustomer()" class="w-full flex flex-col items-center justify-center p-4 bg-neutral-50 dark:bg-neutral-900/50 hover:bg-neutral-100 dark:hover:bg-neutral-900 rounded-lg border border-neutral-200 dark:border-neutral-700 transition-colors duration-200">
<i class="fa-solid fa-user-plus text-2xl text-primary-600 mb-1"></i>
<span class="font-medium">New Customer</span>
<kbd class="text-xs text-neutral-500 dark:text-neutral-400 mt-1">Ctrl+Shift+C</kbd>
</button>
<button onclick="newFile()" class="w-full flex flex-col items-center justify-center p-4 bg-neutral-50 dark:bg-neutral-900/50 hover:bg-neutral-100 dark:hover:bg-neutral-900 rounded-lg border border-neutral-200 dark:border-neutral-700 transition-colors duration-200">
<i class="fa-solid fa-folder-plus text-2xl text-success-600 mb-1"></i>
<span class="font-medium">New File</span>
<kbd class="text-xs text-neutral-500 dark:text-neutral-400 mt-1">Ctrl+Shift+F</kbd>
</button>
</div>
<div class="col-md-6 mb-3">
<div class="d-grid gap-2">
<button class="btn btn-outline-info btn-lg" onclick="newTransaction()">
<i class="bi bi-plus-circle"></i> New Transaction
<small class="d-block text-muted">Ctrl+Shift+T</small>
</button>
<button class="btn btn-outline-warning btn-lg" onclick="globalSearch()">
<i class="bi bi-search"></i> Global Search
<small class="d-block text-muted">Ctrl+F</small>
</button>
</div>
<div class="space-y-3">
<button onclick="newTransaction()" class="w-full flex flex-col items-center justify-center p-4 bg-neutral-50 dark:bg-neutral-900/50 hover:bg-neutral-100 dark:hover:bg-neutral-900 rounded-lg border border-neutral-200 dark:border-neutral-700 transition-colors duration-200">
<i class="fa-solid fa-circle-plus text-2xl text-info-600 mb-1"></i>
<span class="font-medium">New Transaction</span>
<kbd class="text-xs text-neutral-500 dark:text-neutral-400 mt-1">Ctrl+Shift+T</kbd>
</button>
<button onclick="globalSearch()" class="w-full flex flex-col items-center justify-center p-4 bg-neutral-50 dark:bg-neutral-900/50 hover:bg-neutral-100 dark:hover:bg-neutral-900 rounded-lg border border-neutral-200 dark:border-neutral-700 transition-colors duration-200">
<i class="fa-solid fa-magnifying-glass text-2xl text-warning-600 mb-1"></i>
<span class="font-medium">Global Search</span>
<kbd class="text-xs text-neutral-500 dark:text-neutral-400 mt-1">Ctrl+F</kbd>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5><i class="bi bi-clock-history"></i> Recent Activity</h5>
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-soft">
<div class="px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
<h5 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 flex items-center gap-2">
<i class="fa-solid fa-clock-rotate-left"></i>
<span>Recent Activity</span>
</h5>
</div>
<div class="card-body">
<div id="recent-activity">
<p class="text-muted text-center">
<i class="bi bi-hourglass-split"></i><br>
Loading recent activity...
<div class="p-6" id="recent-activity">
<div class="flex flex-col items-center justify-center py-4 text-neutral-500 dark:text-neutral-400">
<i class="fa-solid fa-hourglass-half text-2xl mb-2"></i>
<p>Loading recent activity...</p>
</div>
</div>
</div>
</div>
<!-- System Status -->
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-soft">
<div class="px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
<h5 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 flex items-center gap-2">
<i class="fa-solid fa-circle-info"></i>
<span>System Information</span>
</h5>
</div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-3 text-sm">
<p class="flex justify-between">
<strong>System:</strong>
<span>Delphi Consulting Group Database System</span>
</p>
<p class="flex justify-between">
<strong>Version:</strong>
<span>1.0.0</span>
</p>
<p class="flex justify-between">
<strong>Database:</strong>
<span>SQLite</span>
</p>
</div>
</div>
</div>
</div>
</div>
<!-- System Status -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5><i class="bi bi-info-circle"></i> System Information</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<p><strong>System:</strong> Delphi Consulting Group Database System</p>
<p><strong>Version:</strong> 1.0.0</p>
<p><strong>Database:</strong> SQLite</p>
</div>
<div class="col-md-6">
<p><strong>Last Backup:</strong> <span id="last-backup">Not available</span></p>
<p><strong>Database Size:</strong> <span id="db-size">-</span></p>
<p><strong>Status:</strong> <span id="system-status" class="badge bg-success">Healthy</span></p>
</div>
<div class="space-y-3 text-sm">
<p class="flex justify-between">
<strong>Last Backup:</strong>
<span id="last-backup">Not available</span>
</p>
<p class="flex justify-between">
<strong>Database Size:</strong>
<span id="db-size">-</span>
</p>
<p class="flex justify-between">
<strong>Status:</strong>
<span id="system-status" class="px-2 py-1 bg-success-600 text-white text-xs font-medium rounded-full">Healthy</span>
</p>
</div>
</div>
</div>
@@ -178,55 +188,49 @@
{% block extra_scripts %}
<script>
// Load dashboard data
async function loadDashboardData() {
try {
// This would typically be authenticated API calls
const response = await fetch('/api/admin/stats', {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('token')
}
});
if (response.ok) {
const data = await response.json();
document.getElementById('customer-count').textContent = data.total_customers || '0';
document.getElementById('file-count').textContent = data.total_files || '0';
document.getElementById('transaction-count').textContent = data.total_transactions || '0';
document.getElementById('document-count').textContent = data.total_qdros || '0';
document.getElementById('db-size').textContent = data.database_size || '-';
document.getElementById('last-backup').textContent = data.last_backup || 'Not available';
// Load dashboard data
async function loadDashboardData() {
try {
const response = await fetch('/api/admin/stats', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`
}
} catch (error) {
console.error('Error loading dashboard data:', error);
});
if (response.ok) {
const data = await response.json();
document.getElementById('customer-count').textContent = data.total_customers || '0';
document.getElementById('file-count').textContent = data.total_files || '0';
document.getElementById('transaction-count').textContent = data.total_transactions || '0';
document.getElementById('document-count').textContent = data.total_qdros || '0';
document.getElementById('db-size').textContent = data.database_size || '-';
document.getElementById('last-backup').textContent = data.last_backup || 'Not available';
}
} catch (error) {
console.error('Error loading dashboard data:', error);
}
// Quick action functions
function newCustomer() {
window.location.href = '/customers/new';
}
function newFile() {
window.location.href = '/files/new';
}
function newTransaction() {
window.location.href = '/financial/new';
}
function globalSearch() {
window.location.href = '/search';
}
function showShortcuts() {
const modal = new bootstrap.Modal(document.getElementById('shortcutsModal'));
modal.show();
}
// Load data on page load
document.addEventListener('DOMContentLoaded', function() {
// loadDashboardData(); // Uncomment when authentication is implemented
});
}
// Quick action functions
function newCustomer() {
window.location.href = '/customers';
}
function newFile() {
window.location.href = '/files';
}
function newTransaction() {
window.location.href = '/financial';
}
function globalSearch() {
window.location.href = '/search';
}
// Load data on page load
document.addEventListener('DOMContentLoaded', function() {
loadDashboardData(); // Uncomment when authentication is implemented
});
</script>
{% endblock %}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3,146 +3,164 @@
{% block title %}Data Import - Delphi Database{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2><i class="bi bi-upload"></i> Data Import</h2>
<div class="space-y-6">
<!-- Page Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div class="flex items-center gap-3">
<div class="flex items-center justify-center w-10 h-10 bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 rounded-xl">
<i class="fa-solid fa-upload text-lg"></i>
</div>
<h1 class="text-2xl font-bold text-neutral-900 dark:text-neutral-100">Data Import</h1>
</div>
<div class="flex items-center gap-3">
<button id="refreshStatusBtn" class="bg-info-600 text-white hover:bg-info-700 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors duration-200 flex items-center gap-2">
<i class="fa-solid fa-rotate-right"></i>
<span>Refresh Status</span>
</button>
</div>
</div>
<!-- Import Status Panel -->
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-soft">
<div class="px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
<h5 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 flex items-center gap-2">
<i class="fa-solid fa-circle-info"></i>
<span>Current Database Status</span>
</h5>
</div>
<div class="p-6">
<div id="importStatus">
<div class="flex items-center justify-center py-4 text-neutral-500 dark:text-neutral-400">
<i class="fa-solid fa-rotate-right animate-spin text-xl mr-2"></i>
<p>Loading import status...</p>
</div>
</div>
</div>
</div>
<!-- CSV File Upload Panel -->
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-soft">
<div class="px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
<h5 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 flex items-center gap-2">
<i class="fa-regular fa-file-arrow-up"></i>
<span>Upload CSV Files</span>
</h5>
</div>
<div class="p-6">
<form id="importForm" enctype="multipart/form-data">
<div class="grid grid-cols-1 md:grid-cols-12 gap-4">
<div class="md:col-span-4">
<label for="fileType" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">Data Type *</label>
<select class="w-full px-3 py-3 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200" id="fileType" name="fileType" required>
<option value="">Select data type...</option>
</select>
<div class="text-sm text-neutral-500 dark:text-neutral-400 mt-1" id="fileTypeDescription"></div>
</div>
<div class="md:col-span-6">
<label for="csvFile" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">CSV File *</label>
<input type="file" class="w-full px-3 py-3 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 file:mr-4 file:py-2 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-primary-100 file:text-primary-700 hover:file:bg-primary-200 transition-all duration-200" id="csvFile" name="csvFile" accept=".csv" required>
<div class="text-sm text-neutral-500 dark:text-neutral-400 mt-1">Select the CSV file to import</div>
</div>
<div class="md:col-span-2 flex items-end">
<label class="inline-flex items-center gap-2">
<input class="h-4 w-4 text-primary-600 border-neutral-300 rounded" type="checkbox" id="replaceExisting" name="replaceExisting">
<span>Replace existing data</span>
</label>
</div>
</div>
<div class="mt-4">
<div class="flex items-center gap-3">
<button type="button" class="px-4 py-2 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-200 dark:hover:bg-neutral-600 rounded-lg transition-colors duration-200 flex items-center gap-2" id="validateBtn">
<i class="fa-regular fa-circle-check"></i>
<span>Validate File</span>
</button>
<button type="submit" class="px-4 py-2 bg-primary-600 text-white hover:bg-primary-700 rounded-lg transition-colors duration-200 flex items-center gap-2" id="importBtn">
<i class="fa-solid fa-upload"></i>
<span>Import Data</span>
</button>
</div>
</div>
</form>
</div>
</div>
<!-- Validation Results Panel -->
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-soft hidden" id="validationPanel">
<div class="px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
<h5 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 flex items-center gap-2">
<i class="fa-solid fa-clipboard-check"></i>
<span>File Validation Results</span>
</h5>
</div>
<div class="p-6" id="validationResults">
<!-- Validation results will be shown here -->
</div>
</div>
<!-- Import Progress Panel -->
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-soft hidden" id="progressPanel">
<div class="px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
<h5 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 flex items-center gap-2">
<i class="fa-solid fa-hourglass-half"></i>
<span>Import Progress</span>
</h5>
</div>
<div class="p-6">
<div class="bg-neutral-200 dark:bg-neutral-700 rounded-full h-2 mb-3 overflow-hidden">
<div class="bg-primary-600 h-full rounded-full transition-all duration-300" style="width: 0%" id="progressBar"></div>
</div>
<div id="progressStatus" class="text-sm text-neutral-600 dark:text-neutral-400">Ready to import...</div>
</div>
</div>
<!-- Import Results Panel -->
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-soft hidden" id="resultsPanel">
<div class="px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
<h5 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 flex items-center gap-2">
<i class="fa-solid fa-circle-check"></i>
<span>Import Results</span>
</h5>
</div>
<div class="p-6" id="importResults">
<!-- Import results will be shown here -->
</div>
</div>
<!-- Data Management Panel -->
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-soft">
<div class="px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
<h5 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 flex items-center gap-2">
<i class="fa-solid fa-database"></i>
<span>Data Management</span>
</h5>
</div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<button class="btn btn-info" id="refreshStatusBtn">
<i class="bi bi-arrow-clockwise"></i> Refresh Status
</button>
</div>
</div>
<!-- Import Status Panel -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-info-circle"></i> Current Database Status</h5>
</div>
<div class="card-body">
<div id="importStatus">
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-2">Loading import status...</p>
</div>
<h6 class="text-base font-semibold mb-2">Clear Table Data</h6>
<p class="text-sm text-neutral-500 dark:text-neutral-400 mb-3">Remove all records from a specific table (cannot be undone)</p>
<div class="flex gap-3">
<select class="flex-grow px-3 py-3 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200" id="clearTableType">
<option value="">Select table to clear...</option>
</select>
<button class="px-4 py-3 bg-danger-600 text-white hover:bg-danger-700 rounded-lg transition-colors duration-200 flex items-center gap-2 whitespace-nowrap" id="clearTableBtn">
<i class="fa-solid fa-trash"></i>
<span>Clear Table</span>
</button>
</div>
</div>
</div>
<!-- CSV File Upload Panel -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-file-earmark-arrow-up"></i> Upload CSV Files</h5>
</div>
<div class="card-body">
<form id="importForm" enctype="multipart/form-data">
<div class="row g-3">
<div class="col-md-4">
<label for="fileType" class="form-label">Data Type *</label>
<select class="form-select" id="fileType" name="fileType" required>
<option value="">Select data type...</option>
</select>
<div class="form-text" id="fileTypeDescription"></div>
</div>
<div class="col-md-6">
<label for="csvFile" class="form-label">CSV File *</label>
<input type="file" class="form-control" id="csvFile" name="csvFile" accept=".csv" required>
<div class="form-text">Select the CSV file to import</div>
</div>
<div class="col-md-2">
<label class="form-label">&nbsp;</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="replaceExisting" name="replaceExisting">
<label class="form-check-label" for="replaceExisting">
Replace existing data
</label>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<div class="btn-group" role="group">
<button type="button" class="btn btn-secondary" id="validateBtn">
<i class="bi bi-check-circle"></i> Validate File
</button>
<button type="submit" class="btn btn-primary" id="importBtn">
<i class="bi bi-upload"></i> Import Data
</button>
</div>
</div>
</div>
</form>
</div>
</div>
<!-- Validation Results Panel -->
<div class="card mb-4" id="validationPanel" style="display: none;">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-clipboard-check"></i> File Validation Results</h5>
</div>
<div class="card-body" id="validationResults">
<!-- Validation results will be shown here -->
</div>
</div>
<!-- Import Progress Panel -->
<div class="card mb-4" id="progressPanel" style="display: none;">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-hourglass-split"></i> Import Progress</h5>
</div>
<div class="card-body">
<div class="progress mb-3">
<div class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" style="width: 0%" id="progressBar">0%</div>
</div>
<div id="progressStatus">Ready to import...</div>
</div>
</div>
<!-- Import Results Panel -->
<div class="card mb-4" id="resultsPanel" style="display: none;">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-check-circle-fill"></i> Import Results</h5>
</div>
<div class="card-body" id="importResults">
<!-- Import results will be shown here -->
</div>
</div>
<!-- Data Management Panel -->
<div class="card">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-database"></i> Data Management</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h6>Clear Table Data</h6>
<p class="text-muted small">Remove all records from a specific table (cannot be undone)</p>
<div class="input-group">
<select class="form-select" id="clearTableType">
<option value="">Select table to clear...</option>
</select>
<button class="btn btn-danger" id="clearTableBtn">
<i class="bi bi-trash"></i> Clear Table
</button>
</div>
</div>
<div class="col-md-6">
<h6>Quick Actions</h6>
<div class="d-grid gap-2">
<button class="btn btn-outline-warning" id="backupBtn">
<i class="bi bi-download"></i> Download Backup
</button>
<button class="btn btn-outline-info" id="viewLogsBtn">
<i class="bi bi-journal-text"></i> View Import Logs
</button>
</div>
</div>
<div>
<h6 class="text-base font-semibold mb-2">Quick Actions</h6>
<div class="space-y-3">
<button class="w-full px-4 py-3 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-200 dark:hover:bg-neutral-600 rounded-lg transition-colors duration-200 flex items-center justify-center gap-2" id="backupBtn">
<i class="fa-solid fa-download"></i>
<span>Download Backup</span>
</button>
<button class="w-full px-4 py-3 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-200 dark:hover:bg-neutral-600 rounded-lg transition-colors duration-200 flex items-center justify-center gap-2" id="viewLogsBtn">
<i class="fa-regular fa-file-lines"></i>
<span>View Import Logs</span>
</button>
</div>
</div>
</div>
@@ -250,50 +268,46 @@ async function loadImportStatus() {
} catch (error) {
console.error('Error loading import status:', error);
document.getElementById('importStatus').innerHTML =
'<div class="alert alert-danger">Error loading import status: ' + error.message + '</div>';
'<div class="p-4 bg-danger-100 dark:bg-danger-900/30 text-danger-700 dark:text-danger-300 rounded-lg">Error loading import status: ' + error.message + '</div>';
}
}
function displayImportStatus(status) {
const container = document.getElementById('importStatus');
let html = '<div class="row">';
let html = '<div class="grid grid-cols-1 md:grid-cols-3 gap-4">';
let totalRecords = 0;
Object.entries(status).forEach(([fileType, info], index) => {
Object.entries(status).forEach(([fileType, info]) => {
totalRecords += info.record_count || 0;
const statusClass = info.error ? 'danger' : (info.record_count > 0 ? 'success' : 'secondary');
const statusIcon = info.error ? 'exclamation-triangle' : (info.record_count > 0 ? 'check-circle' : 'circle');
if (index % 3 === 0 && index > 0) {
html += '</div><div class="row mt-2">';
}
const statusClass = info.error ? 'danger' : (info.record_count > 0 ? 'success' : 'neutral');
const statusBg = info.error ? 'bg-danger-100 dark:bg-danger-900/30 border-danger-200 dark:border-danger-800' :
(info.record_count > 0 ? 'bg-success-100 dark:bg-success-900/30 border-success-200 dark:border-success-800' :
'bg-neutral-100 dark:bg-neutral-900/30 border-neutral-200 dark:border-neutral-800');
const statusIcon = info.error ? 'triangle-exclamation text-danger-600' :
(info.record_count > 0 ? 'circle-check text-success-600' : 'circle text-neutral-600');
html += `
<div class="col-md-4">
<div class="card border-${statusClass}">
<div class="card-body p-2">
<div class="d-flex justify-content-between align-items-center">
<div>
<small class="fw-bold">${fileType}</small><br>
<small class="text-muted">${info.table_name}</small>
</div>
<div class="text-end">
<i class="bi bi-${statusIcon} text-${statusClass}"></i><br>
<small class="fw-bold">${info.record_count || 0}</small>
</div>
</div>
${info.error ? `<div class="text-danger small mt-1">${info.error}</div>` : ''}
<div class="bg-white dark:bg-neutral-800 border ${statusBg} rounded-lg p-4">
<div class="flex justify-between items-start">
<div>
<h6 class="font-semibold text-sm text-neutral-900 dark:text-neutral-100">${fileType}</h6>
<p class="text-xs text-neutral-600 dark:text-neutral-400">${info.table_name}</p>
</div>
<div class="text-right">
<i class="fa-solid fa-${statusIcon} text-lg"></i>
<p class="font-bold text-sm text-neutral-900 dark:text-neutral-100 mt-1">${info.record_count || 0}</p>
</div>
</div>
${info.error ? `<p class="text-xs text-danger-600 dark:text-danger-400 mt-2">${info.error}</p>` : ''}
</div>
`;
});
html += '</div>';
html += `<div class="mt-3 text-center">
<strong>Total Records: ${totalRecords.toLocaleString()}</strong>
html += `<div class="mt-4 text-center">
<span class="font-medium text-neutral-900 dark:text-neutral-100">Total Records: ${totalRecords.toLocaleString()}</span>
</div>`;
container.innerHTML = html;
@@ -310,7 +324,7 @@ async function validateFile() {
const fileInput = document.getElementById('csvFile');
if (!fileType || !fileInput.files[0]) {
showAlert('Please select both file type and CSV file', 'warning');
showAlert('Please select both data type and CSV file', 'warning');
return;
}
@@ -350,46 +364,45 @@ function displayValidationResults(result) {
// Overall status
const statusClass = result.valid ? 'success' : 'danger';
const statusIcon = result.valid ? 'check-circle-fill' : 'exclamation-triangle-fill';
const statusIcon = result.valid ? 'circle-check text-success-600' : 'triangle-exclamation text-danger-600';
html += `
<div class="alert alert-${statusClass}">
<i class="bi bi-${statusIcon}"></i>
File validation ${result.valid ? 'passed' : 'failed'}
<div class="p-4 bg-${statusClass}-100 dark:bg-${statusClass}-900/30 rounded-lg mb-4">
<i class="fa-solid fa-${statusIcon} mr-2"></i>
<span class="font-medium">File validation ${result.valid ? 'passed' : 'failed'}</span>
</div>
`;
// Headers validation
html += '<h6>Column Headers</h6>';
html += '<h6 class="text-sm font-semibold mb-2">Column Headers</h6>';
if (result.headers.missing.length > 0) {
html += `<div class="alert alert-warning">
<strong>Missing columns:</strong> ${result.headers.missing.join(', ')}
html += `<div class="p-3 bg-warning-100 dark:bg-warning-900/30 rounded-lg mb-2">
<strong class="text-warning-700 dark:text-warning-300">Missing columns:</strong> ${result.headers.missing.join(', ')}
</div>`;
}
if (result.headers.extra.length > 0) {
html += `<div class="alert alert-info">
<strong>Extra columns:</strong> ${result.headers.extra.join(', ')}
html += `<div class="p-3 bg-info-100 dark:bg-info-900/30 rounded-lg mb-2">
<strong class="text-info-700 dark:text-info-300">Extra columns:</strong> ${result.headers.extra.join(', ')}
</div>`;
}
if (result.headers.missing.length === 0 && result.headers.extra.length === 0) {
html += '<div class="alert alert-success">All expected columns found</div>';
html += '<div class="p-3 bg-success-100 dark:bg-success-900/30 rounded-lg mb-2">All expected columns found</div>';
}
// Sample data
if (result.sample_data && result.sample_data.length > 0) {
html += '<h6>Sample Data (First 10 rows)</h6>';
html += '<div class="table-responsive">';
html += '<table class="table table-sm table-striped">';
html += '<thead><tr>';
html += '<h6 class="text-sm font-semibold mb-2 mt-4">Sample Data (First 10 rows)</h6>';
html += '<div class="overflow-x-auto"><table class="w-full text-sm text-neutral-900 dark:text-neutral-100">';
html += '<thead><tr class="bg-neutral-100 dark:bg-neutral-700">';
Object.keys(result.sample_data[0]).forEach(header => {
html += `<th>${header}</th>`;
html += `<th class="px-3 py-2 text-left font-medium">${header}</th>`;
});
html += '</tr></thead><tbody>';
html += '</tr></thead><tbody class="divide-y divide-neutral-200 dark:divide-neutral-700">';
result.sample_data.forEach(row => {
html += '<tr>';
Object.values(row).forEach(value => {
html += `<td class="small">${value || ''}</td>`;
html += `<td class="px-3 py-2">${value || ''}</td>`;
});
html += '</tr>';
});
@@ -398,19 +411,19 @@ function displayValidationResults(result) {
// Validation errors
if (result.validation_errors && result.validation_errors.length > 0) {
html += '<h6>Data Issues Found</h6>';
html += '<div class="alert alert-warning">';
html += '<h6 class="text-sm font-semibold mb-2 mt-4">Data Issues Found</h6>';
html += '<div class="p-3 bg-warning-100 dark:bg-warning-900/30 rounded-lg">';
result.validation_errors.forEach(error => {
html += `<div>Row ${error.row}, Field "${error.field}": ${error.error}</div>`;
html += `<div class="text-sm"><strong>Row ${error.row}, Field "${error.field}":</strong> ${error.error}</div>`;
});
if (result.total_errors > result.validation_errors.length) {
html += `<div class="mt-2"><strong>... and ${result.total_errors - result.validation_errors.length} more errors</strong></div>`;
html += `<div class="mt-2 text-sm font-medium">... and ${result.total_errors - result.validation_errors.length} more errors</div>`;
}
html += '</div>';
}
container.innerHTML = html;
panel.style.display = 'block';
panel.classList.remove('hidden');
}
async function handleImport(event) {
@@ -426,7 +439,7 @@ async function handleImport(event) {
const replaceExisting = document.getElementById('replaceExisting').checked;
if (!fileType || !fileInput.files[0]) {
showAlert('Please select both file type and CSV file', 'warning');
showAlert('Please select both data type and CSV file', 'warning');
return;
}
@@ -475,30 +488,30 @@ function displayImportResults(result) {
const successClass = result.errors && result.errors.length > 0 ? 'warning' : 'success';
let html = `
<div class="alert alert-${successClass}">
<h6><i class="bi bi-check-circle"></i> Import Completed</h6>
<p class="mb-0">
<strong>File Type:</strong> ${result.file_type}<br>
<strong>Records Imported:</strong> ${result.imported_count}<br>
<strong>Errors:</strong> ${result.total_errors || 0}
</p>
<div class="p-4 bg-${successClass}-100 dark:bg-${successClass}-900/30 rounded-lg mb-4">
<h6 class="font-semibold flex items-center gap-2"><i class="fa-regular fa-circle-check"></i> Import Completed</h6>
<div class="text-sm mt-2 space-y-1">
<p><strong>File Type:</strong> ${result.file_type}</p>
<p><strong>Records Imported:</strong> ${result.imported_count}</p>
<p><strong>Errors:</strong> ${result.total_errors || 0}</p>
</div>
</div>
`;
if (result.errors && result.errors.length > 0) {
html += '<h6>Import Errors</h6>';
html += '<div class="alert alert-danger">';
html += '<h6 class="text-sm font-semibold mb-2">Import Errors</h6>';
html += '<div class="p-3 bg-danger-100 dark:bg-danger-900/30 rounded-lg">';
result.errors.forEach(error => {
html += `<div><strong>Row ${error.row}:</strong> ${error.error}</div>`;
html += `<div class="text-sm"><strong>Row ${error.row}:</strong> ${error.error}</div>`;
});
if (result.total_errors > result.errors.length) {
html += `<div class="mt-2"><strong>... and ${result.total_errors - result.errors.length} more errors</strong></div>`;
html += `<div class="mt-2 text-sm font-medium">... and ${result.total_errors - result.errors.length} more errors</div>`;
}
html += '</div>';
}
container.innerHTML = html;
panel.style.display = 'block';
panel.classList.remove('hidden');
}
function showProgress(show, message = '') {
@@ -509,10 +522,9 @@ function showProgress(show, message = '') {
if (show) {
status.textContent = message;
bar.style.width = '100%';
bar.textContent = 'Processing...';
panel.style.display = 'block';
panel.classList.remove('hidden');
} else {
panel.style.display = 'none';
panel.classList.add('hidden');
}
}
@@ -560,25 +572,13 @@ function viewLogs() {
}
function showAlert(message, type = 'info') {
// Create and show Bootstrap alert
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
alertDiv.style.top = '20px';
alertDiv.style.right = '20px';
alertDiv.style.zIndex = '9999';
alertDiv.innerHTML = `
${message}
<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\"></button>
`;
document.body.appendChild(alertDiv);
// Auto-dismiss after 5 seconds
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 5000);
if (window.alerts && typeof window.alerts.show === 'function') {
window.alerts.show(message, type);
} else if (window.showNotification) {
window.showNotification(message, type);
} else {
alert(String(message));
}
}
</script>
{% endblock %}

View File

@@ -4,78 +4,56 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Delphi Consulting Group Database System</title>
<script src="/static/js/alerts.js"></script>
<!-- Tailwind CSS -->
<link href="/static/css/tailwind.css" rel="stylesheet">
<!-- Icons (Font Awesome) -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<!-- Bootstrap 5.3 CDN -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="/static/css/main.css" rel="stylesheet">
<link href="/static/css/themes.css" rel="stylesheet">
<link href="/static/css/components.css" rel="stylesheet">
</head>
<body class="login-page">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 col-lg-4">
<div class="card login-card shadow-sm mt-5">
<div class="card-body p-5">
<div class="text-center mb-4">
<img src="/static/images/delphi-logo.webp" alt="Delphi Consulting Group" height="60" class="mb-3">
<h2 class="h4 mb-3">Delphi Database System</h2>
<p class="text-muted">Sign in to access the system</p>
</div>
<form id="loginForm" class="login-form" novalidate>
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-person"></i></span>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="invalid-feedback">Please enter your username.</div>
</div>
<div class="mb-4">
<label for="password" class="form-label">Password</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-lock"></i></span>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="invalid-feedback">Please enter your password.</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary" id="loginBtn">
<i class="bi bi-box-arrow-in-right"></i> Sign In
</button>
</div>
</form>
<div class="text-center mt-4">
<small class="text-muted">
Default credentials: admin / admin123
</small>
</div>
</div>
</div>
<!-- System Status -->
<div class="card login-status mt-3">
<div class="card-body text-center py-2">
<small class="text-muted">
<i class="bi bi-shield-check text-success"></i>
Secure connection established
</small>
</div>
</div>
<div class="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900 px-4">
<div class="max-w-md w-full space-y-8 bg-white dark:bg-neutral-800 p-8 rounded-xl shadow-md">
<div class="text-center">
<img src="/static/images/delphi-logo.webp" alt="Delphi Consulting Group" class="mx-auto h-20 w-auto">
<h2 class="mt-6 text-xl font-normal text-gray-900 dark:text-white">Delphi Database System</h2>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">Sign in to access the system</p>
</div>
<form class="mt-8 space-y-6" id="loginForm">
<div class="space-y-4">
<div>
<label for="username" class="sr-only">Username</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-400">
<i class="fa-solid fa-user"></i>
</div>
<input id="username" name="username" type="text" required class="appearance-none rounded-lg relative block w-full px-3 py-2 pl-10 border border-gray-300 placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-primary-500 focus:border-primary-500 focus:z-10 sm:text-sm" placeholder="Username">
</div>
</div>
<div>
<label for="password" class="sr-only">Password</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-400">
<i class="fa-solid fa-lock"></i>
</div>
<input id="password" name="password" type="password" required class="appearance-none rounded-lg relative block w-full px-3 py-2 pl-10 border border-gray-300 placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-primary-500 focus:border-primary-500 focus:z-10 sm:text-sm" placeholder="Password">
</div>
</div>
</div>
<div>
<button type="submit" id="loginBtn" class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-lg text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 transition duration-150 ease-in-out">
Sign in
</button>
</div>
</form>
<p class="mt-2 text-center text-sm text-gray-600 dark:text-gray-400">
Default credentials: admin / admin123
</p>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Check for logout reason
@@ -102,19 +80,22 @@
// Validate form
if (!loginForm.checkValidity()) {
e.stopPropagation();
loginForm.classList.add('was-validated');
loginForm.reportValidity();
return;
}
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
console.log('Attempting login with username:', username);
// Show loading state
const originalText = loginBtn.innerHTML;
loginBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Signing in...';
loginBtn.innerHTML = '<span class="inline-block animate-spin w-4 h-4 border-2 border-white border-t-transparent rounded-full mr-2"></span>Signing in...';
loginBtn.disabled = true;
try {
console.log('Sending request to /api/auth/login');
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {
@@ -126,12 +107,22 @@
})
});
console.log('Response status:', response.status);
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Login failed');
let errorMessage = 'Login failed';
try {
const errorData = await response.json();
console.log('Error data:', errorData);
errorMessage = errorData.detail || errorMessage;
} catch (e) {
console.log('Failed to parse error response');
}
throw new Error(errorMessage);
}
const data = await response.json();
console.log('Login successful, token:', data.access_token);
// Store token
localStorage.setItem('auth_token', data.access_token);
@@ -181,30 +172,11 @@
}
function showAlert(message, type = 'info') {
// Remove existing alerts
const existingAlerts = document.querySelectorAll('.alert');
existingAlerts.forEach(alert => alert.remove());
// Create new alert
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show mt-3`;
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
// Insert before the form
const form = document.getElementById('loginForm');
form.parentNode.insertBefore(alertDiv, form);
// Auto-dismiss success messages
if (type === 'success') {
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 5000);
if (window.alerts && typeof window.alerts.show === 'function') {
window.alerts.show(message, type);
return;
}
alert(String(message));
}
</script>
</body>

File diff suppressed because it is too large Load Diff

View File

@@ -1,138 +1,192 @@
<!-- Support Ticket Modal -->
<div class="modal fade" id="supportModal" tabindex="-1" aria-labelledby="supportModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="supportModalLabel">
<i class="fas fa-bug me-2"></i>Submit Internal Issue
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="supportForm">
<div class="row">
<div class="col-md-6 mb-3">
<label for="contactName" class="form-label">Reporter Name *</label>
<input type="text" class="form-control" id="contactName" required>
<div id="supportModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div class="bg-white dark:bg-neutral-800 rounded-xl shadow-xl max-w-4xl w-full max-h-screen overflow-hidden">
<div class="flex items-center justify-between px-6 py-4 bg-primary-600 text-white">
<h2 class="text-xl font-semibold flex items-center gap-2">
<i class="fas fa-bug"></i>
<span>Submit Internal Issue</span>
</h2>
<button onclick="closeSupportModal()" class="text-primary-200 hover:text-white transition-colors">
<i class="fa-solid fa-xmark text-xl"></i>
</button>
</div>
<div class="px-6 py-4 max-h-96 overflow-y-auto scrollbar-thin">
<form id="supportForm">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label for="contactName" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">Reporter Name *</label>
<input type="text" id="contactName" required class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200">
</div>
<div>
<label for="contactEmail" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">Reporter Email *</label>
<input type="email" id="contactEmail" required class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200">
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label for="ticketCategory" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">Issue Type *</label>
<select id="ticketCategory" required class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200">
<option value="">Select issue type...</option>
<option value="bug_report" selected>Bug Report</option>
<option value="qa_issue">QA Issue</option>
<option value="feature_request">Feature Request</option>
<option value="database_issue">Database Issue</option>
<option value="system_error">System Error</option>
<option value="user_access">User Access</option>
<option value="performance">Performance Issue</option>
<option value="documentation">Documentation</option>
<option value="configuration">Configuration</option>
<option value="testing">Testing Request</option>
</select>
</div>
<div>
<label for="ticketPriority" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">Priority</label>
<select id="ticketPriority" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200">
<option value="low">Low</option>
<option value="medium" selected>Medium</option>
<option value="high">High</option>
<option value="urgent">Urgent</option>
</select>
</div>
</div>
<div class="mb-4">
<label for="ticketSubject" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">Issue Summary *</label>
<input type="text" id="ticketSubject" maxlength="200" required class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200">
<p class="text-sm text-neutral-500 dark:text-neutral-400 mt-1">Brief summary of the bug/issue</p>
</div>
<div class="mb-4">
<label for="ticketDescription" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">Detailed Description *</label>
<textarea id="ticketDescription" rows="5" required class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200 resize-none" placeholder="Steps to reproduce:
1.
2.
3.
Expected behavior:
Actual behavior:
Additional context:"></textarea>
<p class="text-sm text-neutral-500 dark:text-neutral-400 mt-1">Include steps to reproduce, expected vs actual behavior, error messages, etc.</p>
</div>
<!-- System Information -->
<div class="bg-neutral-50 dark:bg-neutral-800/50 rounded-lg border border-neutral-200 dark:border-neutral-700 p-4 mb-4">
<h3 class="text-sm font-semibold text-neutral-700 dark:text-neutral-300 mb-3 flex items-center gap-2">
<i class="fas fa-info-circle text-info-600"></i>
<span>System Information</span>
<span class="text-xs font-normal text-neutral-500 dark:text-neutral-400">(automatically included)</span>
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
<div>
<span class="font-medium text-neutral-600 dark:text-neutral-400">Current Page:</span>
<span id="currentPageInfo" class="text-neutral-900 dark:text-neutral-100 ml-1">Loading...</span>
</div>
<div class="col-md-6 mb-3">
<label for="contactEmail" class="form-label">Reporter Email *</label>
<input type="email" class="form-control" id="contactEmail" required>
<div>
<span class="font-medium text-neutral-600 dark:text-neutral-400">Browser:</span>
<span id="browserInfo" class="text-neutral-900 dark:text-neutral-100 ml-1">Loading...</span>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="ticketCategory" class="form-label">Issue Type *</label>
<select class="form-select" id="ticketCategory" required>
<option value="">Select issue type...</option>
<option value="bug_report" selected>Bug Report</option>
<option value="qa_issue">QA Issue</option>
<option value="feature_request">Feature Request</option>
<option value="database_issue">Database Issue</option>
<option value="system_error">System Error</option>
<option value="user_access">User Access</option>
<option value="performance">Performance Issue</option>
<option value="documentation">Documentation</option>
<option value="configuration">Configuration</option>
<option value="testing">Testing Request</option>
</select>
</div>
<div class="col-md-6 mb-3">
<label for="ticketPriority" class="form-label">Priority</label>
<select class="form-select" id="ticketPriority">
<option value="low">Low</option>
<option value="medium" selected>Medium</option>
<option value="high">High</option>
<option value="urgent">Urgent</option>
</select>
</div>
</div>
<div class="flex items-start gap-3 p-4 bg-info-50 dark:bg-info-900/20 border border-info-200 dark:border-info-800 rounded-lg text-info-800 dark:text-info-300">
<i class="fas fa-info-circle text-info-600 dark:text-info-400 mt-0.5"></i>
<div>
<p class="font-medium">Note:</p>
<p class="text-sm mt-1">Your issue will be assigned a tracking number and the development team will be notified automatically.</p>
</div>
<div class="mb-3">
<label for="ticketSubject" class="form-label">Issue Summary *</label>
<input type="text" class="form-control" id="ticketSubject" maxlength="200" required>
<div class="form-text">Brief summary of the bug/issue</div>
</div>
<div class="mb-3">
<label for="ticketDescription" class="form-label">Detailed Description *</label>
<textarea class="form-control" id="ticketDescription" rows="5" required placeholder="Steps to reproduce:&#10;1. &#10;2. &#10;3. &#10;&#10;Expected behavior:&#10;&#10;Actual behavior:&#10;&#10;Additional context:"></textarea>
<div class="form-text">Include steps to reproduce, expected vs actual behavior, error messages, etc.</div>
</div>
<!-- System Information (auto-populated) -->
<div class="card bg-light mb-3">
<div class="card-body">
<h6 class="card-title">
<i class="fas fa-info-circle me-1"></i>System Information
<small class="text-muted">(automatically included)</small>
</h6>
<div class="row">
<div class="col-md-6">
<small><strong>Current Page:</strong> <span id="currentPageInfo">Loading...</span></small>
</div>
<div class="col-md-6">
<small><strong>Browser:</strong> <span id="browserInfo">Loading...</span></small>
</div>
</div>
</div>
</div>
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
<strong>Note:</strong> Your issue will be assigned a tracking number and the development team will be notified automatically.
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="submitSupportTicket">
<i class="fas fa-bug me-2"></i>Submit Issue
</button>
</div>
</div>
</form>
</div>
<div class="flex items-center justify-end gap-3 px-6 py-4 border-t border-neutral-200 dark:border-neutral-700 bg-neutral-50 dark:bg-neutral-800/50">
<button onclick="closeSupportModal()" class="px-4 py-2 bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-200 dark:hover:bg-neutral-600 rounded-lg transition-colors duration-200">
Cancel
</button>
<button type="button" id="submitSupportTicket" class="flex items-center gap-2 px-4 py-2 bg-primary-600 text-white hover:bg-primary-700 rounded-lg transition-colors duration-200">
<i class="fas fa-bug"></i>
<span>Submit Issue</span>
</button>
</div>
</div>
</div>
<!-- Support Ticket Success Modal -->
<div class="modal fade" id="supportSuccessModal" tabindex="-1" aria-labelledby="supportSuccessLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title" id="supportSuccessLabel">
<i class="fas fa-check-circle me-2"></i>Issue Submitted Successfully
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
<div id="supportSuccessModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div class="bg-white dark:bg-neutral-800 rounded-xl shadow-xl max-w-2xl w-full">
<div class="flex items-center justify-between px-6 py-4 bg-success-600 text-white">
<h2 class="text-xl font-semibold flex items-center gap-2">
<i class="fas fa-check-circle"></i>
<span>Issue Submitted Successfully</span>
</h2>
<button onclick="closeSupportSuccessModal()" class="text-success-200 hover:text-white transition-colors">
<i class="fa-solid fa-xmark text-xl"></i>
</button>
</div>
<div class="px-6 py-8 text-center">
<div class="mb-6">
<div class="w-16 h-16 bg-success-100 dark:bg-success-900/30 rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fas fa-bug text-2xl text-success-600 dark:text-success-400"></i>
</div>
<h3 class="text-xl font-semibold text-neutral-900 dark:text-neutral-100 mb-2">Issue logged successfully!</h3>
</div>
<div class="modal-body text-center">
<div class="mb-3">
<i class="fas fa-bug fa-3x text-success mb-3"></i>
<h4>Issue logged successfully!</h4>
</div>
<div class="alert alert-success">
<strong>Issue ID:</strong> <span id="newTicketNumber"></span>
</div>
<p>Your issue has been logged and the development team has been notified. You'll receive updates on the resolution progress.</p>
<div class="mt-4">
<h6>What happens next?</h6>
<ul class="list-unstyled text-start">
<li><i class="fas fa-check text-success me-2"></i>Issue logged in tracking system</li>
<li><i class="fas fa-users text-warning me-2"></i>Development team has been notified</li>
<li><i class="fas fa-code text-info me-2"></i>Issue will be triaged and prioritized</li>
<li><i class="fas fa-bell text-primary me-2"></i>You'll get status updates via email</li>
</ul>
<div class="bg-success-50 dark:bg-success-900/20 border border-success-200 dark:border-success-800 rounded-lg p-4 mb-6">
<div class="flex items-center justify-center gap-2">
<span class="font-medium text-success-800 dark:text-success-300">Issue ID:</span>
<span id="newTicketNumber" class="font-mono font-semibold text-success-900 dark:text-success-200"></span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" data-bs-dismiss="modal">Close</button>
<p class="text-neutral-600 dark:text-neutral-400 mb-6">
Your issue has been logged and the development team has been notified. You'll receive updates on the resolution progress.
</p>
<div class="text-left">
<h4 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 mb-4 text-center">What happens next?</h4>
<div class="space-y-3">
<div class="flex items-center gap-3">
<div class="w-8 h-8 bg-success-100 dark:bg-success-900/30 rounded-full flex items-center justify-center flex-shrink-0">
<i class="fas fa-check text-success-600 dark:text-success-400 text-sm"></i>
</div>
<span class="text-neutral-700 dark:text-neutral-300">Issue logged in tracking system</span>
</div>
<div class="flex items-center gap-3">
<div class="w-8 h-8 bg-warning-100 dark:bg-warning-900/30 rounded-full flex items-center justify-center flex-shrink-0">
<i class="fas fa-users text-warning-600 dark:text-warning-400 text-sm"></i>
</div>
<span class="text-neutral-700 dark:text-neutral-300">Development team has been notified</span>
</div>
<div class="flex items-center gap-3">
<div class="w-8 h-8 bg-info-100 dark:bg-info-900/30 rounded-full flex items-center justify-center flex-shrink-0">
<i class="fas fa-code text-info-600 dark:text-info-400 text-sm"></i>
</div>
<span class="text-neutral-700 dark:text-neutral-300">Issue will be triaged and prioritized</span>
</div>
<div class="flex items-center gap-3">
<div class="w-8 h-8 bg-primary-100 dark:bg-primary-900/30 rounded-full flex items-center justify-center flex-shrink-0">
<i class="fas fa-bell text-primary-600 dark:text-primary-400 text-sm"></i>
</div>
<span class="text-neutral-700 dark:text-neutral-300">You'll get status updates via email</span>
</div>
</div>
</div>
</div>
<div class="flex items-center justify-end gap-3 px-6 py-4 border-t border-neutral-200 dark:border-neutral-700 bg-neutral-50 dark:bg-neutral-800/50">
<button onclick="closeSupportSuccessModal()" class="px-4 py-2 bg-success-600 text-white hover:bg-success-700 rounded-lg transition-colors duration-200">
Close
</button>
</div>
</div>
</div>
<script>
// Support ticket functionality
// Support ticket functionality - Tailwind version
let supportSystem = {
currentPageInfo: 'Unknown',
browserInfo: 'Unknown',
@@ -176,45 +230,57 @@ let supportSystem = {
this.browserInfo = `${browserName} (${navigator.platform})`;
// Update modal display
document.getElementById('currentPageInfo').textContent = this.currentPageInfo;
document.getElementById('browserInfo').textContent = this.browserInfo;
const currentPageElement = document.getElementById('currentPageInfo');
const browserElement = document.getElementById('browserInfo');
if (currentPageElement) currentPageElement.textContent = this.currentPageInfo;
if (browserElement) browserElement.textContent = this.browserInfo;
},
setupEventListeners: function() {
// Auto-populate user info if logged in
const supportModal = document.getElementById('supportModal');
supportModal.addEventListener('show.bs.modal', this.populateUserInfo.bind(this));
// Submit button
document.getElementById('submitSupportTicket').addEventListener('click', this.submitTicket.bind(this));
const submitBtn = document.getElementById('submitSupportTicket');
if (submitBtn) {
submitBtn.addEventListener('click', this.submitTicket.bind(this));
}
// Form validation
const form = document.getElementById('supportForm');
form.addEventListener('submit', function(e) {
e.preventDefault();
supportSystem.submitTicket();
});
if (form) {
form.addEventListener('submit', function(e) {
e.preventDefault();
supportSystem.submitTicket();
});
}
},
populateUserInfo: function() {
// Try to get current user info from the global app state
if (window.app && window.app.user) {
const user = window.app.user;
document.getElementById('contactName').value = `${user.first_name || ''} ${user.last_name || ''}`.trim() || user.username;
document.getElementById('contactEmail').value = user.email;
const nameInput = document.getElementById('contactName');
const emailInput = document.getElementById('contactEmail');
if (nameInput && !nameInput.value) {
nameInput.value = `${user.first_name || ''} ${user.last_name || ''}`.trim() || user.username;
}
if (emailInput && !emailInput.value) {
emailInput.value = user.email;
}
}
},
submitTicket: async function() {
const form = document.getElementById('supportForm');
if (!form.checkValidity()) {
form.classList.add('was-validated');
// Show validation errors
form.reportValidity();
return;
}
const submitBtn = document.getElementById('submitSupportTicket');
const originalText = submitBtn.innerHTML;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Submitting...';
const originalHTML = submitBtn.innerHTML;
submitBtn.innerHTML = '<i class="fas fa-spinner animate-spin"></i><span class="ml-2">Submitting...</span>';
submitBtn.disabled = true;
try {
@@ -241,15 +307,14 @@ let supportSystem = {
if (response.ok) {
// Hide support modal
bootstrap.Modal.getInstance(document.getElementById('supportModal')).hide();
closeSupportModal();
// Show success modal
document.getElementById('newTicketNumber').textContent = result.ticket_number;
new bootstrap.Modal(document.getElementById('supportSuccessModal')).show();
document.getElementById('supportSuccessModal').classList.remove('hidden');
// Reset form
form.reset();
form.classList.remove('was-validated');
} else {
throw new Error(result.detail || 'Failed to submit ticket');
@@ -257,30 +322,63 @@ let supportSystem = {
} catch (error) {
console.error('Error submitting support ticket:', error);
this.showAlert('Failed to submit support ticket: ' + error.message, 'error');
this.showAlert('Failed to submit support ticket: ' + error.message, 'danger');
} finally {
submitBtn.innerHTML = originalText;
submitBtn.innerHTML = originalHTML;
submitBtn.disabled = false;
}
},
showAlert: function(message, type = 'info') {
// Use existing notification system if available
if (window.showNotification) {
window.showNotification(message, type);
// Use existing alert system if available
if (window.showAlert) {
window.showAlert(message, type);
} else {
alert(message);
}
}
};
// Modal control functions
function openSupportModal() {
supportSystem.populateUserInfo();
document.getElementById('supportModal').classList.remove('hidden');
}
function closeSupportModal() {
document.getElementById('supportModal').classList.add('hidden');
}
function closeSupportSuccessModal() {
document.getElementById('supportSuccessModal').classList.add('hidden');
}
// Close modals when clicking outside
document.addEventListener('click', function(event) {
const supportModal = document.getElementById('supportModal');
const successModal = document.getElementById('supportSuccessModal');
if (event.target === supportModal) {
closeSupportModal();
}
if (event.target === successModal) {
closeSupportSuccessModal();
}
});
// Handle escape key for modals
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeSupportModal();
closeSupportSuccessModal();
}
});
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
supportSystem.init();
});
// Global function to open support modal
window.openSupportModal = function() {
new bootstrap.Modal(document.getElementById('supportModal')).show();
};
// Make function globally available
window.openSupportModal = openSupportModal;
</script>