// Settings page functionality // ========== Legacy functions (kept for backward compatibility) ========== function testPrinter() { const resultEl = document.getElementById('printerTestResult'); const selectedHidden = document.getElementById('selectedPrintersJson'); resultEl.textContent = 'Testing...'; resultEl.style.color = '#666'; const body = JSON.stringify({ selectedPrintersJson: selectedHidden ? selectedHidden.value : '[]' }); fetch('/settings/test-printer', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body }) .then(response => response.json()) .then(data => { if (!data.error) { resultEl.textContent = '✓ ' + data.message; resultEl.style.color = '#28a745'; } else { resultEl.textContent = '✗ ' + (data.message || 'Test failed'); resultEl.style.color = '#dc3545'; } }) .catch(error => { console.error('Test print error:', error); resultEl.textContent = '✗ Network error'; resultEl.style.color = '#dc3545'; }); } async function uploadLogo() { const fileInput = document.getElementById('logoUpload'); const file = fileInput.files[0]; if (!file) { alert('Please select a file first'); return; } const validTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif']; if (!validTypes.includes(file.type)) { alert('Please select a valid image file (PNG, JPG, or GIF)'); return; } if (file.size > 5 * 1024 * 1024) { alert('File size must be less than 5MB'); return; } const formData = new FormData(); formData.append('file', file); try { const response = await fetch('/settings/upload-logo', { method: 'POST', body: formData }); const data = await response.json(); if (!data.error) { alert('Logo uploaded successfully!'); location.reload(); } else { alert('Upload failed: ' + (data.message || 'Unknown error')); } } catch (error) { console.error('Logo upload error:', error); alert('Upload failed: Network error'); } } // ========== New Printer Management Functions ========== let currentPrinterId = null; // Load and display printers async function loadPrinters() { const container = document.getElementById('printer-cards-container'); if (!container) return; try { const response = await fetch('/api/printers/list'); const data = await response.json(); if (data.error || !data.printers || data.printers.length === 0) { container.innerHTML = `

No printers configured yet.

Click "Add Printer" to configure your first printer.

`; return; } // Render printer cards const cardsHTML = data.printers.map(printer => createPrinterCard(printer)).join(''); container.innerHTML = cardsHTML; } catch (error) { console.error('Failed to load printers:', error); container.innerHTML = `
Failed to load printers: ${error.message}
`; } } // Create HTML for a printer card function createPrinterCard(printer) { const defaultBadge = printer.is_default ? 'DEFAULT' : ''; const enabledBadge = printer.is_enabled ? 'ENABLED' : 'DISABLED'; const typeLabel = { 'network': 'Network', 'com': 'Serial/COM', 'usb': 'USB', 'system': 'System Printer' }[printer.type] || printer.type; return `

${escapeHtml(printer.name)}

${defaultBadge} ${enabledBadge}
Connection: ${typeLabel}: ${escapeHtml(printer.interface)}
Paper: ${printer.paper_format} (${printer.paper_width} chars) | ${printer.printer_type}
${!printer.is_default ? `` : ''} ${!printer.is_default ? `` : ''}
`; } // Escape HTML to prevent XSS function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Open add printer modal function openAddPrinterModal() { currentPrinterId = null; document.getElementById('printerModalTitle').textContent = 'Add Printer'; document.getElementById('printerConfigForm').reset(); document.getElementById('printer_id').value = ''; // Set defaults document.getElementById('paper_width').value = 48; document.getElementById('paper_format').value = '80mm'; document.getElementById('printer_type_model').value = 'epson'; document.getElementById('font_size').value = 'normal'; document.getElementById('line_style').value = 'single'; document.getElementById('qr_code_size').value = 3; document.getElementById('qr_code_correction').value = 'M'; document.getElementById('qr_code_content_template').value = 'ORDER-{id}'; document.getElementById('header_text').value = ''; document.getElementById('footer_text').value = ''; document.getElementById('business_contact_size').value = 'normal'; // Pre-fill Business Information from Receipt Template tab try { const tplBusinessName = document.getElementById('businessName'); const tplBusinessAddress = document.getElementById('businessAddress'); const tplBusinessPhone = document.getElementById('businessPhone'); const tplBusinessWebsite = document.getElementById('businessWebsite'); const tplBusinessEmail = document.getElementById('businessEmail'); const tplBusinessContactSize = document.getElementById('businessContactSize'); if (tplBusinessName) document.getElementById('business_name').value = tplBusinessName.value || ''; if (tplBusinessAddress) document.getElementById('business_address').value = tplBusinessAddress.value || ''; if (tplBusinessPhone) document.getElementById('business_phone').value = tplBusinessPhone.value || ''; if (tplBusinessWebsite) document.getElementById('business_website').value = tplBusinessWebsite.value || ''; if (tplBusinessEmail) document.getElementById('business_email').value = tplBusinessEmail.value || ''; if (tplBusinessContactSize) document.getElementById('business_contact_size').value = tplBusinessContactSize.value || 'normal'; } catch (_) {} // Set checkboxes document.getElementById('show_customer_info').checked = true; document.getElementById('show_order_items').checked = true; document.getElementById('show_prices').checked = true; document.getElementById('show_timestamps').checked = true; document.getElementById('qr_code_enabled').checked = true; document.getElementById('is_enabled').checked = true; document.getElementById('is_default').checked = false; showModal('printerConfigModal'); switchPrinterModalTab('connection'); } // Edit printer async function editPrinter(id) { try { const response = await fetch(`/api/printers/${id}`); const data = await response.json(); if (data.error) { alert('Failed to load printer: ' + data.message); return; } const printer = data.printer; currentPrinterId = id; document.getElementById('printerModalTitle').textContent = 'Edit Printer'; document.getElementById('printer_id').value = id; // Fill form with printer data document.getElementById('printer_name').value = printer.name || ''; document.getElementById('printer_type_select').value = printer.type || 'network'; document.getElementById('printer_interface').value = printer.interface || ''; document.getElementById('paper_format').value = printer.paper_format || '80mm'; document.getElementById('paper_width').value = printer.paper_width || 48; document.getElementById('printer_type_model').value = printer.printer_type || 'epson'; document.getElementById('font_size').value = printer.font_size || 'normal'; document.getElementById('line_style').value = printer.line_style || 'single'; document.getElementById('header_text').value = printer.header_text || ''; document.getElementById('footer_text').value = printer.footer_text || ''; document.getElementById('business_name').value = printer.business_name || ''; document.getElementById('business_address').value = printer.business_address || ''; document.getElementById('business_phone').value = printer.business_phone || ''; document.getElementById('business_website').value = printer.business_website || ''; document.getElementById('business_email').value = printer.business_email || ''; document.getElementById('business_contact_size').value = printer.business_contact_size || 'normal'; document.getElementById('logo_path').value = printer.logo_path || ''; document.getElementById('logo_max_width_dots').value = printer.logo_max_width_dots || ''; if (printer.logo_path) { document.getElementById('logo_preview').innerHTML = `Current logo: ${printer.logo_path}`; } else { document.getElementById('logo_preview').innerHTML = ''; } document.getElementById('qr_code_size').value = printer.qr_code_size || 3; document.getElementById('qr_code_correction').value = printer.qr_code_correction || 'M'; document.getElementById('qr_code_content_template').value = printer.qr_code_content_template || 'ORDER-{id}'; document.getElementById('show_customer_info').checked = printer.show_customer_info !== false; document.getElementById('show_order_items').checked = printer.show_order_items !== false; document.getElementById('show_prices').checked = printer.show_prices !== false; document.getElementById('show_timestamps').checked = printer.show_timestamps !== false; document.getElementById('qr_code_enabled').checked = printer.qr_code_enabled !== false; document.getElementById('is_default').checked = printer.is_default || false; document.getElementById('is_enabled').checked = printer.is_enabled !== false; showModal('printerConfigModal'); switchPrinterModalTab('connection'); } catch (error) { console.error('Failed to load printer:', error); alert('Failed to load printer: ' + error.message); } } // Save printer configuration async function savePrinterConfig() { const form = document.getElementById('printerConfigForm'); if (!form.checkValidity()) { form.reportValidity(); return; } const formData = new FormData(form); const config = {}; for (const [key, value] of formData.entries()) { if (key === 'id' && !value) continue; // Skip empty id // Handle checkboxes if (['show_customer_info', 'show_order_items', 'show_prices', 'show_timestamps', 'qr_code_enabled', 'is_default', 'is_enabled'].includes(key)) { config[key] = document.getElementById(key).checked; } else if (key === 'paper_width' || key === 'qr_code_size' || key === 'logo_max_width_dots') { const val = parseInt(value, 10); if (!isNaN(val) && val > 0) config[key] = val; } else { config[key] = value; } } // Ensure checkbox fields are always present in payload (unchecked boxes are omitted from FormData by default) ['show_customer_info', 'show_order_items', 'show_prices', 'show_timestamps', 'qr_code_enabled', 'is_default', 'is_enabled'].forEach((key) => { const el = document.getElementById(key); if (el) { config[key] = !!el.checked; } }); try { const printerId = document.getElementById('printer_id').value; const url = printerId ? `/api/printers/${printerId}` : '/api/printers/create'; const method = printerId ? 'PUT' : 'POST'; const response = await fetch(url, { method: method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) }); const data = await response.json(); if (data.error) { alert('Failed to save printer: ' + data.message); return; } alert(data.message || 'Printer saved successfully'); closePrinterModal(); loadPrinters(); } catch (error) { console.error('Failed to save printer:', error); alert('Failed to save printer: ' + error.message); } } // Delete printer async function deletePrinter(id) { if (!confirm('Are you sure you want to delete this printer?')) { return; } try { const response = await fetch(`/api/printers/${id}`, { method: 'DELETE' }); const data = await response.json(); if (data.error) { alert('Failed to delete printer: ' + data.message); return; } alert('Printer deleted successfully'); loadPrinters(); } catch (error) { console.error('Failed to delete printer:', error); alert('Failed to delete printer: ' + error.message); } } // Test printer async function testPrinterById(id) { try { const response = await fetch(`/api/printers/${id}/test`, { method: 'POST' }); const data = await response.json(); if (data.error) { alert('Test failed: ' + data.message); } else { alert(data.message || 'Test print sent successfully'); } } catch (error) { console.error('Test print error:', error); alert('Test failed: ' + error.message); } } // Set default printer async function setDefaultPrinter(id) { try { const response = await fetch(`/api/printers/${id}/set-default`, { method: 'POST' }); const data = await response.json(); if (data.error) { alert('Failed to set default: ' + data.message); } else { loadPrinters(); } } catch (error) { console.error('Failed to set default:', error); alert('Failed to set default: ' + error.message); } } // Toggle printer enabled async function togglePrinterEnabled(id) { try { const response = await fetch(`/api/printers/${id}/toggle-enabled`, { method: 'POST' }); const data = await response.json(); if (data.error) { alert('Failed to toggle printer: ' + data.message); } else { loadPrinters(); } } catch (error) { console.error('Failed to toggle printer:', error); alert('Failed to toggle printer: ' + error.message); } } // Upload logo for specific printer async function uploadLogoForPrinter() { const fileInput = document.getElementById('logo_upload_modal'); const file = fileInput.files[0]; if (!file) { alert('Please select a file first'); return; } const validTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif']; if (!validTypes.includes(file.type)) { alert('Please select a valid image file (PNG, JPG, or GIF)'); return; } if (file.size > 5 * 1024 * 1024) { alert('File size must be less than 5MB'); return; } const formData = new FormData(); formData.append('file', file); // If editing existing printer, include printer_id const printerId = document.getElementById('printer_id').value; if (printerId) { formData.append('printer_id', printerId); } try { const response = await fetch('/settings/upload-logo', { method: 'POST', body: formData }); const data = await response.json(); if (!data.error) { alert('Logo uploaded successfully'); document.getElementById('logo_path').value = data.filepath; document.getElementById('logo_preview').innerHTML = `Logo uploaded: ${data.filepath}`; } else { alert('Upload failed: ' + (data.message || 'Unknown error')); } } catch (error) { console.error('Logo upload error:', error); alert('Upload failed: Network error'); } } // Detect printers for modal async function detectPrintersForModal() { const listEl = document.getElementById('detected-printers-list'); listEl.innerHTML = '

Detecting printers...

'; try { const response = await fetch('/api/printers/detect'); const data = await response.json(); if (data.error || !data.printers || data.printers.length === 0) { listEl.innerHTML = '

No printers detected.

'; return; } const items = data.printers.map(p => { const typeLabel = { 'system': 'System', 'com': 'COM' }[p.type] || p.type; return `
${escapeHtml(p.name)} (${typeLabel})
`; }).join(''); listEl.innerHTML = items; } catch (error) { console.error('Failed to detect printers:', error); listEl.innerHTML = '

Failed to detect printers.

'; } } // Select a detected printer function selectDetectedPrinter(type, interface) { document.getElementById('printer_type_select').value = type; document.getElementById('printer_interface').value = interface; updateInterfaceHint(); } // Update interface hint based on connection type function updateInterfaceHint() { const type = document.getElementById('printer_type_select').value; const hintEl = document.getElementById('interface_hint'); const hints = { 'network': 'Enter IP:Port for network printers (e.g., 192.168.1.100:9100)', 'com': 'Enter COM port (e.g., COM1, COM3)', 'usb': 'Enter USB device path (e.g., /dev/usb/lp0)', 'system': 'Enter the exact printer name from Windows' }; hintEl.textContent = hints[type] || 'Enter connection address'; } // Auto-update paper width when format changes function updatePaperWidthFromFormat() { const format = document.getElementById('paper_format').value; const widthInput = document.getElementById('paper_width'); const widthMap = { '58mm': 32, '80mm': 48, 'letter': 80 }; if (widthMap[format]) { widthInput.value = widthMap[format]; } } // Modal tab switching function switchPrinterModalTab(tabName) { // Update tab buttons document.querySelectorAll('.printer-modal-tab-btn').forEach(btn => { btn.classList.remove('active'); if (btn.getAttribute('data-tab') === tabName) { btn.classList.add('active'); } }); // Update tab content document.querySelectorAll('.printer-modal-tab-content').forEach(content => { content.classList.remove('active'); }); document.getElementById(tabName + '-tab-content').classList.add('active'); } // Close printer modal function closePrinterModal() { hideModal('printerConfigModal'); currentPrinterId = null; } // Show modal function showModal(modalId) { document.getElementById(modalId).classList.add('visible'); } // Hide modal function hideModal(modalId) { document.getElementById(modalId).classList.remove('visible'); } // ========== Sound Notification Functions ========== // Upload sound file async function uploadSound(soundType) { const fileInputId = soundType === 'newOrder' ? 'newOrderSoundUpload' : 'canceledOrderSoundUpload'; const fileInput = document.getElementById(fileInputId); const file = fileInput.files[0]; if (!file) { alert('Please select a file first'); return; } const validTypes = ['audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg']; if (!validTypes.includes(file.type)) { alert('Please select a valid audio file (MP3, WAV, or OGG)'); return; } if (file.size > 10 * 1024 * 1024) { alert('File size must be less than 10MB'); return; } const formData = new FormData(); formData.append('file', file); formData.append('soundType', soundType); try { const response = await fetch('/settings/upload-sound', { method: 'POST', body: formData }); const data = await response.json(); if (!data.error) { alert('Sound uploaded successfully! Please save settings to apply changes.'); location.reload(); } else { alert('Upload failed: ' + (data.message || 'Unknown error')); } } catch (error) { console.error('Sound upload error:', error); alert('Upload failed: Network error'); } } // Test sound playback async function testSound(soundType) { try { // Get current settings const response = await fetch('/api/notification-settings'); const data = await response.json(); if (data.error) { alert('Failed to load sound settings'); return; } const soundPath = soundType === 'newOrder' ? (data.newOrderSoundPath || '/public/sounds/new-order-notification.mp3') : (data.canceledOrderSoundPath || '/public/sounds/canceled-order-notification.mp3'); const volumeInput = document.getElementById('soundVolume'); const volume = volumeInput ? parseInt(volumeInput.value, 10) / 100 : 0.8; const audio = new Audio(soundPath); audio.volume = volume; audio.play().catch(error => { console.error('Failed to play sound:', error); alert('Failed to play sound. Make sure the file exists and is a valid audio file.'); }); } catch (error) { console.error('Test sound error:', error); alert('Failed to test sound: ' + error.message); } } // Update volume display function updateVolumeDisplay() { const volumeInput = document.getElementById('soundVolume'); const volumeValue = document.getElementById('volumeValue'); if (volumeInput && volumeValue) { volumeValue.textContent = volumeInput.value; } } // ========== Event Listeners ========== document.addEventListener('DOMContentLoaded', function() { // Load printers on settings page if (document.getElementById('printer-cards-container')) { loadPrinters(); } // Printer modal tab switching document.querySelectorAll('.printer-modal-tab-btn').forEach(btn => { btn.addEventListener('click', function() { switchPrinterModalTab(this.getAttribute('data-tab')); }); }); // Connection type change handler const typeSelect = document.getElementById('printer_type_select'); if (typeSelect) { typeSelect.addEventListener('change', updateInterfaceHint); } // Paper format change handler const formatSelect = document.getElementById('paper_format'); if (formatSelect) { formatSelect.addEventListener('change', updatePaperWidthFromFormat); } // Volume slider handler const volumeInput = document.getElementById('soundVolume'); if (volumeInput) { volumeInput.addEventListener('input', updateVolumeDisplay); } // Update interface placeholder based on interface type (legacy support) const interfaceSelect = document.getElementById('printerInterface'); const pathInput = document.getElementById('printerPath'); if (interfaceSelect && pathInput) { interfaceSelect.addEventListener('change', function() { const placeholders = { 'usb': '/dev/usb/lp0 (Linux) or COM1 (Windows)', 'network': '192.168.1.100:9100', 'serial': 'COM1 (Windows) or /dev/ttyS0 (Linux)' }; pathInput.placeholder = placeholders[this.value] || 'Enter printer path'; }); } });