progress
This commit is contained in:
@@ -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">
|
||||
© <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">
|
||||
© <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>
|
||||
Reference in New Issue
Block a user