fixes and refactor

This commit is contained in:
HotSwapp
2025-08-14 19:16:28 -05:00
parent 5111079149
commit bfc04a6909
61 changed files with 5689 additions and 767 deletions

View File

@@ -137,6 +137,10 @@
<i class="fa-solid fa-wrench"></i>
<span>Maintenance</span>
</button>
<button class="flex items-center gap-2 px-6 py-4 text-sm font-medium border-b-2 border-transparent hover:border-primary-300 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 hover:bg-neutral-50 dark:hover:bg-neutral-700/50 transition-all duration-200" id="printers-tab" data-tab-target="#printers" type="button" role="tab">
<i class="fa-solid fa-print"></i>
<span>Printers</span>
</button>
<button class="flex items-center gap-2 px-6 py-4 text-sm font-medium border-b-2 border-transparent hover:border-primary-300 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 hover:bg-neutral-50 dark:hover:bg-neutral-700/50 transition-all duration-200" id="import-tab" data-tab-target="#import" type="button" role="tab">
<i class="fa-solid fa-file-import"></i>
<span>Import</span>
@@ -513,6 +517,110 @@
</div>
</div>
<!-- Printers Tab -->
<div id="printers" role="tabpanel" class="hidden">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="lg:col-span-1">
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow">
<div class="px-4 py-3 border-b border-neutral-200 dark:border-neutral-700 flex items-center justify-between">
<h5 class="m-0 font-semibold"><i class="fa-solid fa-list"></i> Printers</h5>
<button type="button" class="px-3 py-1.5 bg-primary-600 hover:bg-primary-700 text-white rounded text-sm" onclick="showCreatePrinterForm()">
<i class="fas fa-plus"></i> Add
</button>
</div>
<div class="p-4">
<ul id="printers-list" class="divide-y divide-neutral-200 dark:divide-neutral-700">
<li class="py-2 text-neutral-500">Loading...</li>
</ul>
</div>
</div>
</div>
<div class="lg:col-span-2">
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow">
<div class="px-4 py-3 border-b border-neutral-200 dark:border-neutral-700">
<h5 class="m-0 font-semibold"><i class="fa-solid fa-pen-to-square"></i> Edit Printer</h5>
</div>
<div class="p-4">
<form id="printer-form" class="grid grid-cols-1 md:grid-cols-2 gap-4" onsubmit="return savePrinter(event)">
<div>
<label class="block text-sm font-medium mb-1">Printer Name *</label>
<input id="printer_name" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg" required>
</div>
<div>
<label class="block text-sm font-medium mb-1">Description</label>
<input id="description" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg">
</div>
<div>
<label class="block text-sm font-medium mb-1">Driver</label>
<input id="driver" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg">
</div>
<div>
<label class="block text-sm font-medium mb-1">Port</label>
<input id="port" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg">
</div>
<div>
<label class="block text-sm font-medium mb-1">Number</label>
<input id="number" type="number" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg">
</div>
<div class="flex items-center gap-2 mt-6">
<input id="default_printer" type="checkbox" class="rounded border-neutral-300 text-primary-600 focus:ring-primary-500">
<label class="text-sm">Default Printer</label>
</div>
<div class="md:col-span-2 grid grid-cols-1 md:grid-cols-2 gap-4 mt-2">
<div>
<label class="block text-sm font-medium mb-1">Page Break</label>
<input id="page_break" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg" placeholder="e.g., \f">
</div>
<div>
<label class="block text-sm font-medium mb-1">Setup Sequence</label>
<input id="setup_st" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg">
</div>
<div>
<label class="block text-sm font-medium mb-1">Reset Sequence</label>
<input id="reset_st" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg">
</div>
<div>
<label class="block text-sm font-medium mb-1">Bold Start</label>
<input id="b_bold" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg">
</div>
<div>
<label class="block text-sm font-medium mb-1">Bold End</label>
<input id="e_bold" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg">
</div>
<div>
<label class="block text-sm font-medium mb-1">Underline Start</label>
<input id="b_underline" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg">
</div>
<div>
<label class="block text-sm font-medium mb-1">Underline End</label>
<input id="e_underline" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg">
</div>
</div>
<div class="md:col-span-2 grid grid-cols-2 md:grid-cols-4 gap-3 mt-2">
<label class="inline-flex items-center gap-2"><input id="phone_book" type="checkbox" class="rounded border-neutral-300 text-primary-600 focus:ring-primary-500"> Phone Book</label>
<label class="inline-flex items-center gap-2"><input id="rolodex_info" type="checkbox" class="rounded border-neutral-300 text-primary-600 focus:ring-primary-500"> Rolodex Info</label>
<label class="inline-flex items-center gap-2"><input id="envelope" type="checkbox" class="rounded border-neutral-300 text-primary-600 focus:ring-primary-500"> Envelope</label>
<label class="inline-flex items-center gap-2"><input id="file_cabinet" type="checkbox" class="rounded border-neutral-300 text-primary-600 focus:ring-primary-500"> File Cabinet</label>
<label class="inline-flex items-center gap-2"><input id="accounts" type="checkbox" class="rounded border-neutral-300 text-primary-600 focus:ring-primary-500"> Accounts</label>
<label class="inline-flex items-center gap-2"><input id="statements" type="checkbox" class="rounded border-neutral-300 text-primary-600 focus:ring-primary-500"> Statements</label>
<label class="inline-flex items-center gap-2"><input id="calendar" type="checkbox" class="rounded border-neutral-300 text-primary-600 focus:ring-primary-500"> Calendar</label>
</div>
<div class="md:col-span-2 flex gap-2 mt-4">
<button type="submit" class="px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white rounded">
<i class="fa-solid fa-floppy-disk"></i> Save
</button>
<button type="button" class="px-4 py-2 border border-neutral-300 dark:border-neutral-600 rounded" onclick="clearPrinterForm()">Clear</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Data Import Tab -->
<div id="import" role="tabpanel" class="hidden">
<div class="flex flex-wrap -mx-4">
@@ -1203,6 +1311,8 @@ function onTabShown(tabName) {
loadUsers();
} else if (tabName === 'settings') {
loadSettings();
} else if (tabName === 'printers') {
loadPrinters();
}
}
@@ -1221,6 +1331,7 @@ document.addEventListener('DOMContentLoaded', function() {
loadSettings();
loadLookupTables();
loadBackups();
loadPrinters();
// Tabs setup
initializeTabs();
@@ -1720,6 +1831,157 @@ async function loadLookupTables() {
}
}
// Printers Management
async function loadPrinters() {
try {
const response = await window.http.wrappedFetch('/api/admin/printers');
const printers = await response.json();
renderPrintersList(printers);
} catch (err) {
console.error('Failed to load printers:', err);
const ul = document.getElementById('printers-list');
if (ul) ul.innerHTML = '<li class="py-2 text-danger-600 dark:text-danger-400">Failed to load printers</li>';
}
}
function renderPrintersList(printers) {
const ul = document.getElementById('printers-list');
if (!ul) return;
if (!printers || printers.length === 0) {
ul.innerHTML = '<li class="py-2 text-neutral-500">No printers found</li>';
return;
}
ul.innerHTML = printers.map(p => `
<li class="py-2 px-2 rounded hover:bg-neutral-50 dark:hover:bg-neutral-800/50">
<div class="flex justify-between items-center gap-2">
<div class="flex-1 cursor-pointer" onclick="selectPrinter('${encodeURIComponent(p.printer_name)}')">
<div class="font-medium">${p.printer_name}</div>
<div class="text-xs text-neutral-500">${p.description || ''}</div>
</div>
<div class="flex items-center gap-2">
${p.default_printer ? '<span class="text-xs px-2 py-0.5 bg-primary-100 dark:bg-primary-800 text-primary-700 dark:text-primary-200 rounded">Default</span>' : ''}
<button class="px-2 py-1 border border-danger-600 text-danger-700 dark:text-danger-200 rounded text-xs hover:bg-danger-50 dark:hover:bg-danger-900/20" onclick="deletePrinter(event, '${encodeURIComponent(p.printer_name)}')" title="Delete">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</li>
`).join('');
}
async function selectPrinter(encodedName) {
const name = decodeURIComponent(encodedName);
try {
const response = await window.http.wrappedFetch('/api/admin/printers/' + encodeURIComponent(name));
const p = await response.json();
fillPrinterForm(p);
} catch (err) {
console.error('Failed to fetch printer:', err);
if (window.alerts) window.alerts.error('Failed to load printer details');
}
}
function fillPrinterForm(p) {
const set = (id, val) => { const el = document.getElementById(id); if (el) el.value = val == null ? '' : val; };
const setb = (id, val) => { const el = document.getElementById(id); if (el) el.checked = !!val; };
set('printer_name', p.printer_name || '');
set('description', p.description);
set('driver', p.driver);
set('port', p.port);
set('number', p.number);
setb('default_printer', p.default_printer);
set('page_break', p.page_break);
set('setup_st', p.setup_st);
set('reset_st', p.reset_st);
set('b_bold', p.b_bold);
set('e_bold', p.e_bold);
set('b_underline', p.b_underline);
set('e_underline', p.e_underline);
setb('phone_book', p.phone_book);
setb('rolodex_info', p.rolodex_info);
setb('envelope', p.envelope);
setb('file_cabinet', p.file_cabinet);
setb('accounts', p.accounts);
setb('statements', p.statements);
setb('calendar', p.calendar);
}
function showCreatePrinterForm() {
clearPrinterForm();
const nameEl = document.getElementById('printer_name');
if (nameEl) nameEl.focus();
}
function clearPrinterForm() {
fillPrinterForm({});
}
async function savePrinter(event) {
event.preventDefault();
const payload = {
printer_name: document.getElementById('printer_name').value,
description: document.getElementById('description').value,
driver: document.getElementById('driver').value,
port: document.getElementById('port').value,
number: document.getElementById('number').value ? parseInt(document.getElementById('number').value, 10) : null,
default_printer: document.getElementById('default_printer').checked,
page_break: document.getElementById('page_break').value,
setup_st: document.getElementById('setup_st').value,
reset_st: document.getElementById('reset_st').value,
b_bold: document.getElementById('b_bold').value,
e_bold: document.getElementById('e_bold').value,
b_underline: document.getElementById('b_underline').value,
e_underline: document.getElementById('e_underline').value,
phone_book: document.getElementById('phone_book').checked,
rolodex_info: document.getElementById('rolodex_info').checked,
envelope: document.getElementById('envelope').checked,
file_cabinet: document.getElementById('file_cabinet').checked,
accounts: document.getElementById('accounts').checked,
statements: document.getElementById('statements').checked,
calendar: document.getElementById('calendar').checked,
};
try {
const existsResp = await window.http.wrappedFetch('/api/admin/printers/' + encodeURIComponent(payload.printer_name));
if (existsResp.ok) {
// update
const resp = await window.http.wrappedFetch('/api/admin/printers/' + encodeURIComponent(payload.printer_name), {
method: 'PUT',
body: JSON.stringify(payload),
});
if (!resp.ok) throw await window.http.toError(resp, 'Failed to update printer');
} else {
// create
const resp = await window.http.wrappedFetch('/api/admin/printers', {
method: 'POST',
body: JSON.stringify(payload),
});
if (!resp.ok) throw await window.http.toError(resp, 'Failed to create printer');
}
if (window.alerts) window.alerts.success('Printer saved');
await loadPrinters();
} catch (err) {
console.error(err);
if (window.alerts) window.alerts.error(window.http.formatAlert(err, 'Printer save failed'));
}
return false;
}
async function deletePrinter(evt, encodedName) {
evt.stopPropagation();
const name = decodeURIComponent(encodedName);
if (!confirm(`Delete printer "${name}"?`)) return;
try {
const resp = await window.http.wrappedFetch('/api/admin/printers/' + encodeURIComponent(name), { method: 'DELETE' });
if (!resp.ok) throw await window.http.toError(resp, 'Failed to delete printer');
if (window.alerts) window.alerts.success('Printer deleted');
await loadPrinters();
clearPrinterForm();
} catch (err) {
console.error(err);
if (window.alerts) window.alerts.error(window.http.formatAlert(err, 'Delete failed'));
}
}
async function vacuumDatabase() {
if (!confirm('This will optimize the database. Continue?')) return;
@@ -2576,6 +2838,17 @@ function displayAdminImportResults(result) {
html += '</div>';
}
// Add summary for printers
if (result.file_type === 'PRINTERS.csv') {
html += `
<div class="mt-2 p-2 bg-neutral-50 dark:bg-neutral-800/50 rounded border border-neutral-200 dark:border-neutral-700 text-sm">
<strong>Printers:</strong> ${result.created_count || 0} created, ${result.updated_count || 0} updated
</div>
`;
// Auto-refresh printers tab list
try { loadPrinters(); } catch (_) {}
}
container.innerHTML = html;
panel.style.display = 'block';
}