progress
This commit is contained in:
1307
templates/admin.html
1307
templates/admin.html
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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"> </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 %}
|
||||
@@ -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
@@ -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: 1. 2. 3. Expected behavior: Actual behavior: 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>
|
||||
Reference in New Issue
Block a user