fixes and refactor
This commit is contained in:
@@ -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';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user