From d277d5c07b71aef1c8ba98957389b6028c4eec74 Mon Sep 17 00:00:00 2001 From: James Pattinson Date: Sat, 7 Feb 2026 07:01:31 -0500 Subject: [PATCH] Notifications and printing --- frontend/app.js | 190 +++++++++++++++++++++++++++++++++++++------- frontend/index.html | 52 ++++++++++++ frontend/styles.css | 92 +++++++++++++++++++++ 3 files changed, 307 insertions(+), 27 deletions(-) diff --git a/frontend/app.js b/frontend/app.js index cbcfab4..6b0e00e 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -7,6 +7,37 @@ let expandedDrugs = new Set(); let currentUser = null; let accessToken = null; +// Toast notification system +function showToast(message, type = 'info', duration = 3000) { + const container = document.getElementById('toastContainer'); + if (!container) return; + + const toast = document.createElement('div'); + toast.className = `toast ${type}`; + + const icons = { + success: '✓', + error: '✕', + warning: '⚠', + info: 'ℹ' + }; + + toast.innerHTML = ` + ${icons[type] || icons.info} + ${message} + `; + + container.appendChild(toast); + + // Auto remove after duration + setTimeout(() => { + toast.classList.add('fade-out'); + setTimeout(() => { + container.removeChild(toast); + }, 300); + }, duration); +} + // Initialize on page load document.addEventListener('DOMContentLoaded', () => { checkAuth(); @@ -139,11 +170,13 @@ function setupEventListeners() { const variantForm = document.getElementById('variantForm'); const editVariantForm = document.getElementById('editVariantForm'); const dispenseForm = document.getElementById('dispenseForm'); + const prescribeForm = document.getElementById('prescribeForm'); const editForm = document.getElementById('editForm'); const addModal = document.getElementById('addModal'); const addVariantModal = document.getElementById('addVariantModal'); const editVariantModal = document.getElementById('editVariantModal'); const dispenseModal = document.getElementById('dispenseModal'); + const prescribeModal = document.getElementById('prescribeModal'); const editModal = document.getElementById('editModal'); const addDrugBtn = document.getElementById('addDrugBtn'); const dispenseBtn = document.getElementById('dispenseBtn'); @@ -151,6 +184,7 @@ function setupEventListeners() { const cancelVariantBtn = document.getElementById('cancelVariantBtn'); const cancelEditVariantBtn = document.getElementById('cancelEditVariantBtn'); const cancelDispenseBtn = document.getElementById('cancelDispenseBtn'); + const cancelPrescribeBtn = document.getElementById('cancelPrescribeBtn'); const cancelEditBtn = document.getElementById('cancelEditBtn'); const showAllBtn = document.getElementById('showAllBtn'); const showLowStockBtn = document.getElementById('showLowStockBtn'); @@ -166,6 +200,7 @@ function setupEventListeners() { if (variantForm) variantForm.addEventListener('submit', handleAddVariant); if (editVariantForm) editVariantForm.addEventListener('submit', handleEditVariant); if (dispenseForm) dispenseForm.addEventListener('submit', handleDispenseDrug); + if (prescribeForm) prescribeForm.addEventListener('submit', handlePrescribeDrug); if (editForm) editForm.addEventListener('submit', handleEditDrug); if (addDrugBtn) addDrugBtn.addEventListener('click', () => openModal(addModal)); @@ -178,6 +213,7 @@ function setupEventListeners() { if (cancelVariantBtn) cancelVariantBtn.addEventListener('click', () => closeModal(addVariantModal)); if (cancelEditVariantBtn) cancelEditVariantBtn.addEventListener('click', () => closeModal(editVariantModal)); if (cancelDispenseBtn) cancelDispenseBtn.addEventListener('click', () => closeModal(dispenseModal)); + if (cancelPrescribeBtn) cancelPrescribeBtn.addEventListener('click', () => closeModal(prescribeModal)); if (cancelEditBtn) cancelEditBtn.addEventListener('click', closeEditModal); const closeHistoryBtn = document.getElementById('closeHistoryBtn'); @@ -358,6 +394,7 @@ function renderDrugs() {
+ @@ -435,10 +472,10 @@ async function handleAddDrug(e) { document.getElementById('initialVariantThreshold').value = '10'; closeModal(document.getElementById('addModal')); await loadDrugs(); - alert('Drug added successfully!'); + showToast('Drug added successfully!', 'success'); } catch (error) { console.error('Error adding drug:', error); - alert('Failed to add drug. Check the console for details.'); + showToast('Failed to add drug. Check the console for details.', 'error'); } } @@ -453,7 +490,7 @@ async function handleDispenseDrug(e) { const notes = document.getElementById('dispenseNotes').value; if (!variantId || isNaN(quantity) || quantity <= 0 || !userName) { - alert('Please fill in all required fields (Drug Variant, Quantity > 0, Dispensed by)'); + showToast('Please fill in all required fields (Drug Variant, Quantity > 0, Dispensed by)', 'warning'); return; } @@ -479,10 +516,10 @@ async function handleDispenseDrug(e) { document.getElementById('dispenseForm').reset(); closeModal(document.getElementById('dispenseModal')); await loadDrugs(); - alert('Drug dispensed successfully!'); + showToast('Drug dispensed successfully!', 'success'); } catch (error) { console.error('Error dispensing drug:', error); - alert('Failed to dispense drug: ' + error.message); + showToast('Failed to dispense drug: ' + error.message, 'error'); } } @@ -548,10 +585,10 @@ async function handleAddVariant(e) { closeModal(document.getElementById('addVariantModal')); await loadDrugs(); renderDrugs(); - alert('Variant added successfully!'); + showToast('Variant added successfully!', 'success'); } catch (error) { console.error('Error adding variant:', error); - alert('Failed to add variant. Check the console for details.'); + showToast('Failed to add variant. Check the console for details.', 'error'); } } @@ -598,10 +635,10 @@ async function handleEditVariant(e) { closeModal(document.getElementById('editVariantModal')); await loadDrugs(); renderDrugs(); - alert('Variant updated successfully!'); + showToast('Variant updated successfully!', 'success'); } catch (error) { console.error('Error updating variant:', error); - alert('Failed to update variant. Check the console for details.'); + showToast('Failed to update variant. Check the console for details.', 'error'); } } @@ -618,6 +655,105 @@ function dispenseVariant(variantId) { openModal(document.getElementById('dispenseModal')); } +// Prescribe variant and print label +function prescribeVariant(variantId, drugName, variantStrength, unit) { + // Set hidden fields + document.getElementById('prescribeVariantId').value = variantId; + document.getElementById('prescribeDrugName').value = drugName; + document.getElementById('prescribeVariantStrength').value = variantStrength; + document.getElementById('prescribeUnit').value = unit || 'units'; + + // Pre-fill user name if available + if (currentUser) { + document.getElementById('prescribeUser').value = currentUser.username; + } + + // Set default expiry date to 1 month from now + const defaultExpiry = new Date(); + defaultExpiry.setMonth(defaultExpiry.getMonth() + 1); + document.getElementById('prescribeExpiry').value = defaultExpiry.toISOString().split('T')[0]; + + // Open prescribe modal + openModal(document.getElementById('prescribeModal')); +} + +// Handle prescribe drug form submission +async function handlePrescribeDrug(e) { + e.preventDefault(); + + const variantId = parseInt(document.getElementById('prescribeVariantId').value); + const drugName = document.getElementById('prescribeDrugName').value; + const variantStrength = document.getElementById('prescribeVariantStrength').value; + const unit = document.getElementById('prescribeUnit').value; + const quantity = parseFloat(document.getElementById('prescribeQuantity').value); + const animalName = document.getElementById('prescribeAnimal').value; + const dosage = document.getElementById('prescribeDosage').value; + const expiryDate = document.getElementById('prescribeExpiry').value; + const userName = document.getElementById('prescribeUser').value; + const notes = document.getElementById('prescribeNotes').value; + + if (!variantId || isNaN(quantity) || quantity <= 0 || !animalName || !dosage || !expiryDate || !userName) { + showToast('Please fill in all required fields', 'warning'); + return; + } + + // Convert expiry date to DD/MM/YYYY format + const expiryParts = expiryDate.split('-'); + const formattedExpiry = `${expiryParts[2]}/${expiryParts[1]}/${expiryParts[0]}`; + + try { + // First, dispense the drug (decrement inventory) + const dispensingData = { + drug_variant_id: variantId, + quantity: quantity, + animal_name: animalName, + user_name: userName, + notes: notes || null + }; + + const dispenseResponse = await apiCall('/dispense', { + method: 'POST', + body: JSON.stringify(dispensingData) + }); + + if (!dispenseResponse.ok) { + const error = await dispenseResponse.json(); + throw new Error(error.detail || 'Failed to dispense drug'); + } + + // Second, print the label + const labelData = { + variables: { + practice_name: "Many Tears Animal Rescue", + animal_name: animalName, + drug_name: `${drugName} ${variantStrength}`, + dosage: dosage, + quantity: `${quantity} ${unit}`, + expiry_date: formattedExpiry + } + }; + + const labelResponse = await apiCall('/labels/print', { + method: 'POST', + body: JSON.stringify(labelData) + }); + + if (!labelResponse.ok) { + console.error('Label printing failed, but drug was dispensed'); + showToast('Drug prescribed successfully, but label printing failed', 'warning', 5000); + } else { + showToast('Drug prescribed and label sent to printer!', 'success'); + } + + document.getElementById('prescribeForm').reset(); + closeModal(document.getElementById('prescribeModal')); + await loadDrugs(); + } catch (error) { + console.error('Error prescribing drug:', error); + showToast('Failed to prescribe drug: ' + error.message, 'error'); + } +} + // Delete variant async function deleteVariant(variantId) { if (!confirm('Are you sure you want to delete this variant?')) return; @@ -631,10 +767,10 @@ async function deleteVariant(variantId) { await loadDrugs(); renderDrugs(); - alert('Variant deleted successfully!'); + showToast('Variant deleted successfully!', 'success'); } catch (error) { console.error('Error deleting variant:', error); - alert('Failed to delete variant. Check the console for details.'); + showToast('Failed to delete variant. Check the console for details.', 'error'); } } @@ -730,10 +866,10 @@ async function handleEditDrug(e) { closeEditModal(); await loadDrugs(); - alert('Drug updated successfully!'); + showToast('Drug updated successfully!', 'success'); } catch (error) { console.error('Error updating drug:', error); - alert('Failed to update drug. Check the console for details.'); + showToast('Failed to update drug. Check the console for details.', 'error'); } } @@ -749,10 +885,10 @@ async function deleteDrug(drugId) { if (!response.ok) throw new Error('Failed to delete drug'); await loadDrugs(); - alert('Drug deleted successfully!'); + showToast('Drug deleted successfully!', 'success'); } catch (error) { console.error('Error deleting drug:', error); - alert('Failed to delete drug. Check the console for details.'); + showToast('Failed to delete drug. Check the console for details.', 'error'); } } @@ -776,12 +912,12 @@ async function handleChangePassword(e) { const confirmPassword = document.getElementById('confirmNewPassword').value; if (newPassword !== confirmPassword) { - alert('New passwords do not match!'); + showToast('New passwords do not match!', 'warning'); return; } if (newPassword.length < 1) { - alert('New password cannot be empty!'); + showToast('New password cannot be empty!', 'warning'); return; } @@ -799,11 +935,11 @@ async function handleChangePassword(e) { throw new Error(error.detail || 'Failed to change password'); } - alert('Password changed successfully!'); + showToast('Password changed successfully!', 'success'); closeModal(document.getElementById('changePasswordModal')); } catch (error) { console.error('Error changing password:', error); - alert('Failed to change password: ' + error.message); + showToast('Failed to change password: ' + error.message, 'error'); } } @@ -823,12 +959,12 @@ async function handleAdminChangePassword(e) { const confirmPassword = document.getElementById('adminChangePasswordConfirm').value; if (newPassword !== confirmPassword) { - alert('Passwords do not match!'); + showToast('Passwords do not match!', 'warning'); return; } if (newPassword.length < 1) { - alert('Password cannot be empty!'); + showToast('Password cannot be empty!', 'warning'); return; } @@ -845,12 +981,12 @@ async function handleAdminChangePassword(e) { throw new Error(error.detail || 'Failed to change password'); } - alert('Password changed successfully!'); + showToast('Password changed successfully!', 'success'); closeModal(document.getElementById('adminChangePasswordModal')); openUserManagement(); } catch (error) { console.error('Error changing password:', error); - alert('Failed to change password: ' + error.message); + showToast('Failed to change password: ' + error.message, 'error'); } } @@ -932,11 +1068,11 @@ async function createUser(e) { document.getElementById('newUsername').value = ''; document.getElementById('newUserPassword').value = ''; - alert('User created successfully!'); + showToast('User created successfully!', 'success'); openUserManagement(); } catch (error) { console.error('Error creating user:', error); - alert('Failed to create user: ' + error.message); + showToast('Failed to create user: ' + error.message, 'error'); } } @@ -949,10 +1085,10 @@ async function deleteUser(userId) { if (!response.ok) throw new Error('Failed to delete user'); - alert('User deleted successfully!'); + showToast('User deleted successfully!', 'success'); openUserManagement(); } catch (error) { console.error('Error deleting user:', error); - alert('Failed to delete user: ' + error.message); + showToast('Failed to delete user: ' + error.message, 'error'); } } diff --git a/frontend/index.html b/frontend/index.html index 32da6c1..7505912 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -7,6 +7,9 @@ + +
+ + + +