Files
delphi-database-v2/sync_result.html
HotSwapp 2e2380552e Customer 360: extended Client fields, auto-migrate, updated Rolodex CRUD/templates, QDRO routes/views, importer mapping
QDRO links appear in rolodex_view.html case rows and case.html header when QDRO data exists, matching legacy flows.
2025-10-13 14:04:35 -05:00

578 lines
26 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Panel - Delphi Database</title>
<!-- Bootstrap 5 CSS CDN -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="http://localhost:8000/static/css/custom.css" rel="stylesheet">
</head>
<body class="">
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<a class="navbar-brand" href="/">
Delphi Database
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<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="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link " href="/dashboard">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link " href="/rolodex">Rolodex</a>
</li>
<li class="nav-item">
<a class="nav-link " href="/payments">Payments</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/admin">Admin</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle me-1"></i>admin
</a>
<ul class="dropdown-menu" aria-labelledby="userDropdown">
<li><a class="dropdown-item" href="/profile">Profile</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="/logout">Logout</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<!-- Main Content -->
<main class="container-fluid mt-4">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<h1 class="mb-4">
<i class="bi bi-gear me-2"></i>Admin Panel
</h1>
<!-- Alert Messages -->
<!-- Upload Section -->
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="bi bi-upload me-2"></i>File Upload
</h5>
</div>
<div class="card-body">
<form action="/admin/upload" method="post" enctype="multipart/form-data">
<div class="mb-3">
<label for="files" class="form-label">
<i class="bi bi-file-earmark-spreadsheet me-2"></i>Select CSV Files
</label>
<input type="file" class="form-control" id="files" name="files" multiple accept=".csv">
<div class="form-text">
<strong>Supported formats:</strong> ROLODEX, PHONE, FILES, LEDGER, PAYMENTS, DEPOSITS, QDROS, PENSIONS, PLANINFO,
TRNSTYPE, TRNSLKUP, FOOTERS, FILESTAT, EMPLOYEE, GRUPLKUP, FILETYPE, and all related tables (*.csv)
</div>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="auto_import" name="auto_import" checked>
<label class="form-check-label" for="auto_import">
<strong>Auto-import after upload (follows Import Order Guide)</strong>
<br>
<small class="text-muted">Will stop on the first file that reports any row errors.</small>
</label>
</div>
<button type="submit" class="btn btn-primary">
<i class="bi bi-cloud-upload me-2"></i>Upload Files
</button>
</form>
</div>
</div>
<!-- Upload Results -->
<!-- Auto Import Results -->
<!-- Upload Errors -->
<!-- Database Status -->
<!-- Import Order Guide -->
<div class="card mb-4">
<div class="card-header bg-info text-white">
<h5 class="mb-0">
<i class="bi bi-list-ol me-2"></i>Import Order Guide
</h5>
</div>
<div class="card-body">
<p class="mb-3">For best results, import tables in this recommended order:</p>
<div class="row">
<div class="col-md-6">
<h6 class="text-primary"><i class="bi bi-1-circle me-2"></i>Reference Tables (Import First)</h6>
<ul class="list-unstyled ms-3">
<li><i class="bi bi-arrow-right me-2"></i>TRNSTYPE</li>
<li><i class="bi bi-arrow-right me-2"></i>TRNSLKUP</li>
<li><i class="bi bi-arrow-right me-2"></i>FOOTERS</li>
<li><i class="bi bi-arrow-right me-2"></i>FILESTAT</li>
<li><i class="bi bi-arrow-right me-2"></i>EMPLOYEE</li>
<li><i class="bi bi-arrow-right me-2"></i>GRUPLKUP</li>
<li><i class="bi bi-arrow-right me-2"></i>FILETYPE</li>
<li><i class="bi bi-arrow-right me-2"></i>FVARLKUP, RVARLKUP</li>
</ul>
</div>
<div class="col-md-6">
<h6 class="text-success"><i class="bi bi-2-circle me-2"></i>Core Data Tables</h6>
<ul class="list-unstyled ms-3">
<li><i class="bi bi-arrow-right me-2"></i>ROLODEX</li>
<li><i class="bi bi-arrow-right me-2"></i>PHONE, ROLEX_V</li>
<li><i class="bi bi-arrow-right me-2"></i>FILES (+ FILES_R, FILES_V, FILENOTS)</li>
<li><i class="bi bi-arrow-right me-2"></i>LEDGER</li>
<li><i class="bi bi-arrow-right me-2"></i>DEPOSITS, PAYMENTS</li>
<li><i class="bi bi-arrow-right me-2"></i>PLANINFO</li>
<li><i class="bi bi-arrow-right me-2"></i>QDROS, PENSIONS (+ related tables)</li>
</ul>
</div>
</div>
<div class="alert alert-warning mt-3 mb-0">
<i class="bi bi-exclamation-triangle me-2"></i>
<strong>Important:</strong> Reference tables must be imported before core data to avoid foreign key errors.
</div>
</div>
</div>
<!-- Sync to Modern Models -->
<div class="card mb-4">
<div class="card-header bg-success text-white">
<h5 class="mb-0">
<i class="bi bi-arrow-repeat me-2"></i>Sync to Modern Models
</h5>
</div>
<div class="card-body">
<p>After importing legacy CSV data, sync it to the simplified modern application models (Client, Phone, Case, Transaction, Payment, Document).</p>
<form action="/admin/sync" method="post" id="syncForm">
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="clearExisting" name="clear_existing" value="true">
<label class="form-check-label" for="clearExisting">
<strong>Clear existing modern data before sync</strong>
<br>
<small class="text-muted">Warning: This will delete all current Client, Phone, Case, Transaction, Payment, and Document records!</small>
</label>
</div>
</div>
<button type="button" class="btn btn-success" onclick="confirmSync()">
<i class="bi bi-arrow-repeat me-2"></i>Start Sync Process
</button>
</form>
</div>
</div>
<!-- Sync Results -->
<div class="card mb-4">
<div class="card-header bg-success text-white">
<h5 class="mb-0">
<i class="bi bi-check-circle me-2"></i>Sync Results
</h5>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-3">
<div class="card bg-light">
<div class="card-body text-center">
<h3 class="mb-0 text-success">78360</h3>
<small class="text-muted">Records Synced</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-light">
<div class="card-body text-center">
<h3 class="mb-0 text-warning">26579</h3>
<small class="text-muted">Records Skipped</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-light">
<div class="card-body text-center">
<h3 class="mb-0 text-danger">26579</h3>
<small class="text-muted">Errors</small>
</div>
</div>
</div>
</div>
<h6 class="mb-3">Detailed Results by Table:</h6>
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>Modern Table</th>
<th>Synced</th>
<th>Skipped</th>
<th>Errors</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Clients</strong></td>
<td class="text-success">52076</td>
<td class="text-warning">0</td>
<td class="text-danger">0</td>
</tr>
<tr>
<td><strong>Phones</strong></td>
<td class="text-success">26284</td>
<td class="text-warning">10</td>
<td class="text-danger">10</td>
</tr>
<tr>
<td colspan="4">
<details>
<summary class="text-danger">View Errors (10)</summary>
<ul class="mt-2 mb-0">
<li><small>No client found for rolodex ID: PINES BACH</small></li>
<li><small>No client found for rolodex ID: PINES BACH</small></li>
<li><small>No client found for rolodex ID: SPORLEDER\CL</small></li>
<li><small>No client found for rolodex ID: SPORLEDER\CL</small></li>
<li><small>No client found for rolodex ID: VOITH_SULZER</small></li>
<li><small>No client found for rolodex ID: VOITH_SULZER</small></li>
<li><small>No client found for rolodex ID: VOIT\T</small></li>
<li><small>No client found for rolodex ID: VOIT\T</small></li>
<li><small>No client found for rolodex ID: ZUEHLSDORF-MACK</small></li>
<li><small>No client found for rolodex ID: ZUEHLSDORF-MACK</small></li>
</ul>
</details>
</td>
</tr>
<tr>
<td><strong>Cases</strong></td>
<td class="text-success">0</td>
<td class="text-warning">0</td>
<td class="text-danger">0</td>
</tr>
<tr>
<td><strong>Transactions</strong></td>
<td class="text-success">0</td>
<td class="text-warning">0</td>
<td class="text-danger">0</td>
</tr>
<tr>
<td><strong>Payments</strong></td>
<td class="text-success">0</td>
<td class="text-warning">0</td>
<td class="text-danger">0</td>
</tr>
<tr>
<td><strong>Documents</strong></td>
<td class="text-success">0</td>
<td class="text-warning">26569</td>
<td class="text-danger">26569</td>
</tr>
<tr>
<td colspan="4">
<details>
<summary class="text-danger">View Errors (26569)</summary>
<ul class="mt-2 mb-0">
<li><small>No case found for file: 1989.001</small></li>
<li><small>No case found for file: 1989.0011</small></li>
<li><small>No case found for file: 1989.0012</small></li>
<li><small>No case found for file: 1989.0012</small></li>
<li><small>No case found for file: 1992.175B</small></li>
<li><small>No case found for file: 1994.064B</small></li>
<li><small>No case found for file: 1995.047</small></li>
<li><small>No case found for file: 1995.047</small></li>
<li><small>No case found for file: 1995.097</small></li>
<li><small>No case found for file: 1995.105</small></li>
<li><small><em>... and 26559 more errors</em></small></li>
</ul>
</details>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Import Section -->
<div class="card mb-4">
<div class="card-header bg-warning">
<h5 class="mb-0">
<i class="bi bi-arrow-down-circle me-2"></i>Data Import
</h5>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>No CSV files available for import. Upload files first.
</div>
</div>
</div>
<!-- Import Results -->
<!-- Recent Import History -->
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Auto-refresh import status for running imports
function refreshRunningImports() {
const runningImports = document.querySelectorAll('span.badge.bg-warning');
if (runningImports.length > 0) {
// In a real application, you might implement WebSocket or polling here
setTimeout(refreshRunningImports, 5000); // Check every 5 seconds
}
}
// Start refresh cycle if there are running imports
refreshRunningImports();
// Select All functionality
document.querySelectorAll('.select-all-btn').forEach(button => {
button.addEventListener('click', function() {
const importType = this.getAttribute('data-import-type');
const form = this.closest('form');
const checkboxes = form.querySelectorAll('.file-checkbox');
const submitBtn = form.querySelector('button[type="submit"]');
// Toggle all checkboxes in this form
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
checkboxes.forEach(checkbox => {
checkbox.checked = !allChecked;
});
// Update button text
this.innerHTML = allChecked ?
'<i class="bi bi-check-all me-1"></i>Select All' :
'<i class="bi bi-dash-square me-1"></i>Deselect All';
// Update submit button state
const hasSelection = Array.from(checkboxes).some(cb => cb.checked);
submitBtn.disabled = !hasSelection;
});
});
// File selection helpers
document.querySelectorAll('.file-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', function() {
const form = this.closest('form');
const checkboxes = form.querySelectorAll('.file-checkbox');
const submitBtn = form.querySelector('button[type="submit"]');
const selectAllBtn = form.querySelector('.select-all-btn');
// Enable/disable submit button based on selection
const hasSelection = Array.from(checkboxes).some(cb => cb.checked);
submitBtn.disabled = !hasSelection;
// Update select all button state
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
const noneChecked = Array.from(checkboxes).every(cb => !cb.checked);
if (allChecked) {
selectAllBtn.innerHTML = '<i class="bi bi-dash-square me-1"></i>Deselect All';
} else if (noneChecked) {
selectAllBtn.innerHTML = '<i class="bi bi-check-all me-1"></i>Select All';
} else {
selectAllBtn.innerHTML = '<i class="bi bi-check-square me-1"></i>Select All';
}
});
});
// Initialize submit buttons as disabled
document.querySelectorAll('form[action*="/admin/import/"]').forEach(form => {
const submitBtn = form.querySelector('button[type="submit"]');
if (submitBtn) {
submitBtn.disabled = true;
}
});
});
// Sync confirmation function
function confirmSync() {
const clearCheckbox = document.getElementById('clearExisting');
const clearExisting = clearCheckbox.checked;
let message = "Are you sure you want to sync legacy data to modern models?";
if (clearExisting) {
message += "\n\n⚠ WARNING: This will DELETE all existing Client, Phone, Case, Transaction, Payment, and Document records before syncing!";
}
if (confirm(message)) {
document.getElementById('syncForm').submit();
}
}
// Delete file function
async function deleteFile(filename, event) {
// Prevent label click from triggering checkbox
event.preventDefault();
event.stopPropagation();
if (!confirm(`Are you sure you want to delete "${filename}"?\n\nThis action cannot be undone.`)) {
return;
}
try {
const response = await fetch(`/admin/delete-file/${encodeURIComponent(filename)}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
});
if (response.ok) {
// Reload the page to refresh the file list
window.location.reload();
} else {
const error = await response.json();
alert(`Error deleting file: ${error.detail || 'Unknown error'}`);
}
} catch (error) {
console.error('Error deleting file:', error);
alert(`Error deleting file: ${error.message}`);
}
}
// Map selected unknown files to a chosen import type
async function mapSelectedFiles(buttonEl, importType) {
// Find the surrounding card and form
const cardBody = buttonEl.closest('.card-body');
const form = cardBody.querySelector('form');
const selectEl = cardBody.querySelector('select.form-select');
if (!form || !selectEl) return;
// Collect selected filenames
const checked = Array.from(form.querySelectorAll('.file-checkbox:checked'))
.map(cb => cb.value);
if (checked.length === 0) {
alert('Select at least one file to map.');
return;
}
const targetType = selectEl.value;
if (!targetType) {
alert('Choose a target type.');
return;
}
buttonEl.disabled = true;
try {
const resp = await fetch('/admin/map-files', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ target_type: targetType, filenames: checked })
});
if (!resp.ok) {
const err = await resp.json().catch(() => ({}));
throw new Error(err.detail || 'Mapping failed');
}
// Refresh UI
window.location.reload();
} catch (e) {
console.error(e);
alert(`Mapping failed: ${e.message}`);
} finally {
buttonEl.disabled = false;
}
}
</script>
</main>
<!-- Footer -->
<footer class="bg-light text-center text-muted mt-5 py-3">
<div class="container">
<small>&copy; 2025 Delphi Database. All rights reserved.</small>
</div>
</footer>
<!-- Bootstrap 5 JS Bundle CDN -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Custom JS -->
<script src="http://localhost:8000/static/js/custom.js"></script>
</body>
</html>