const API_URL = '/api';
let allDrugs = [];
let currentDrug = null;
let showLowStockOnly = false;
let searchTerm = '';
let expandedDrugs = new Set();
// Initialize
document.addEventListener('DOMContentLoaded', () => {
const drugForm = document.getElementById('drugForm');
const variantForm = document.getElementById('variantForm');
const editVariantForm = document.getElementById('editVariantForm');
const dispenseForm = document.getElementById('dispenseForm');
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 editModal = document.getElementById('editModal');
const addDrugBtn = document.getElementById('addDrugBtn');
const cancelAddBtn = document.getElementById('cancelAddBtn');
const cancelVariantBtn = document.getElementById('cancelVariantBtn');
const cancelEditVariantBtn = document.getElementById('cancelEditVariantBtn');
const cancelDispenseBtn = document.getElementById('cancelDispenseBtn');
const cancelEditBtn = document.getElementById('cancelEditBtn');
const showAllBtn = document.getElementById('showAllBtn');
const showLowStockBtn = document.getElementById('showLowStockBtn');
// Modal close buttons
const closeButtons = document.querySelectorAll('.close');
drugForm.addEventListener('submit', handleAddDrug);
variantForm.addEventListener('submit', handleAddVariant);
editVariantForm.addEventListener('submit', handleEditVariant);
dispenseForm.addEventListener('submit', handleDispenseDrug);
editForm.addEventListener('submit', handleEditDrug);
addDrugBtn.addEventListener('click', () => openModal(addModal));
cancelAddBtn.addEventListener('click', () => closeModal(addModal));
cancelVariantBtn.addEventListener('click', () => closeModal(addVariantModal));
cancelEditVariantBtn.addEventListener('click', () => closeModal(editVariantModal));
cancelDispenseBtn.addEventListener('click', () => closeModal(dispenseModal));
cancelEditBtn.addEventListener('click', closeEditModal);
const closeHistoryBtn = document.getElementById('closeHistoryBtn');
closeHistoryBtn.addEventListener('click', () => closeModal(document.getElementById('historyModal')));
closeButtons.forEach(btn => btn.addEventListener('click', (e) => {
const modal = e.target.closest('.modal');
closeModal(modal);
}));
showAllBtn.addEventListener('click', () => {
showLowStockOnly = false;
updateFilterButtons();
renderDrugs();
});
showLowStockBtn.addEventListener('click', () => {
showLowStockOnly = true;
updateFilterButtons();
renderDrugs();
});
// Search functionality
const drugSearch = document.getElementById('drugSearch');
if (drugSearch) {
let searchTimeout;
drugSearch.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
searchTerm = e.target.value.toLowerCase().trim();
renderDrugs();
}, 150);
});
}
// Close modal when clicking outside
window.addEventListener('click', (e) => {
if (e.target.classList.contains('modal')) {
closeModal(e.target);
}
});
loadDrugs();
});
// Load drugs from API
async function loadDrugs() {
try {
const response = await fetch(`${API_URL}/drugs`);
if (!response.ok) throw new Error('Failed to load drugs');
allDrugs = await response.json();
renderDrugs();
updateDispenseDrugSelect();
} catch (error) {
console.error('Error loading drugs:', error);
document.getElementById('drugsList').innerHTML =
'
Error loading drugs. Make sure the backend is running on http://localhost:8000
';
}
}
// Modal utility functions
function openModal(modal) {
modal.classList.add('show');
document.body.style.overflow = 'hidden';
}
function closeModal(modal) {
modal.classList.remove('show');
document.body.style.overflow = 'auto';
}
function closeEditModal() {
closeModal(document.getElementById('editModal'));
}
function updateDispenseDrugSelect() {
const select = document.getElementById('dispenseDrugSelect');
select.innerHTML = '';
allDrugs.forEach(drug => {
drug.variants.forEach(variant => {
const option = document.createElement('option');
option.value = variant.id;
option.textContent = `${drug.name} ${variant.strength} (${variant.quantity} ${variant.unit})`;
select.appendChild(option);
});
});
}
// Render drugs list
function renderDrugs() {
const drugsList = document.getElementById('drugsList');
let drugsToShow = allDrugs;
// Apply search filter
if (searchTerm) {
drugsToShow = drugsToShow.filter(drug =>
drug.name.toLowerCase().includes(searchTerm) ||
(drug.description && drug.description.toLowerCase().includes(searchTerm)) ||
drug.variants.some(variant => variant.strength.toLowerCase().includes(searchTerm))
);
}
// Apply stock filter
if (showLowStockOnly) {
drugsToShow = drugsToShow.filter(drug =>
drug.variants.some(variant => variant.quantity <= variant.low_stock_threshold)
);
}
if (drugsToShow.length === 0) {
drugsList.innerHTML = 'No drugs found matching your criteria
';
return;
}
drugsList.innerHTML = drugsToShow.map(drug => {
const totalVariants = drug.variants.length;
const lowStockVariants = drug.variants.filter(v => v.quantity <= v.low_stock_threshold).length;
const totalQuantity = drug.variants.reduce((sum, v) => sum + v.quantity, 0);
const isLowStock = lowStockVariants > 0;
const isExpanded = expandedDrugs.has(drug.id);
const variantsHtml = isExpanded ? `
${drug.variants.map(variant => {
const variantIsLowStock = variant.quantity <= variant.low_stock_threshold;
return `
${escapeHtml(drug.name)} ${escapeHtml(variant.strength)}
${variant.quantity} ${escapeHtml(variant.unit)}
${variantIsLowStock ? 'Low Stock' : 'OK'}
`;
}).join('')}` : '';
return `
${escapeHtml(drug.name)}
${drug.description ? escapeHtml(drug.description) : 'No description'}
${totalVariants} variant${totalVariants !== 1 ? 's' : ''} (${totalQuantity} total units)
${isLowStock ? `${lowStockVariants} low` : 'All OK'}
${isExpanded ? 'â–¼' : 'â–¶'}
${variantsHtml}
`;
}).join('');
}
// Handle add drug form
async function handleAddDrug(e) {
e.preventDefault();
const drugData = {
name: document.getElementById('drugName').value,
description: document.getElementById('drugDescription').value
};
try {
// Create the drug first
const drugResponse = await fetch(`${API_URL}/drugs`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(drugData)
});
if (!drugResponse.ok) throw new Error('Failed to add drug');
const createdDrug = await drugResponse.json();
// Check if initial variant data was provided
const variantStrength = document.getElementById('initialVariantStrength').value.trim();
if (variantStrength) {
const variantData = {
strength: variantStrength,
quantity: parseFloat(document.getElementById('initialVariantQuantity').value) || 0,
unit: document.getElementById('initialVariantUnit').value || 'units',
low_stock_threshold: parseFloat(document.getElementById('initialVariantThreshold').value) || 10
};
const variantResponse = await fetch(`${API_URL}/drugs/${createdDrug.id}/variants`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(variantData)
});
if (!variantResponse.ok) throw new Error('Failed to add variant');
}
document.getElementById('drugForm').reset();
document.getElementById('initialVariantUnit').value = 'units';
document.getElementById('initialVariantThreshold').value = '10';
closeModal(document.getElementById('addModal'));
await loadDrugs();
alert('Drug added successfully!');
} catch (error) {
console.error('Error adding drug:', error);
alert('Failed to add drug. Check the console for details.');
}
}
// Handle dispense drug form
async function handleDispenseDrug(e) {
e.preventDefault();
const variantId = parseInt(document.getElementById('dispenseDrugSelect').value);
const quantity = parseFloat(document.getElementById('dispenseQuantity').value);
const animalName = document.getElementById('dispenseAnimal').value;
const userName = document.getElementById('dispenseUser').value;
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)');
return;
}
const dispensingData = {
drug_variant_id: variantId,
quantity: quantity,
animal_name: animalName,
user_name: userName,
notes: notes || null
};
try {
const response = await fetch(`${API_URL}/dispense`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(dispensingData)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Failed to dispense drug');
}
document.getElementById('dispenseForm').reset();
closeModal(document.getElementById('dispenseModal'));
await loadDrugs();
alert('Drug dispensed successfully!');
} catch (error) {
console.error('Error dispensing drug:', error);
alert('Failed to dispense drug: ' + error.message);
}
}
// Open edit modal
function openEditModal(drugId) {
const drug = allDrugs.find(d => d.id === drugId);
if (!drug) return;
document.getElementById('editDrugId').value = drug.id;
document.getElementById('editDrugName').value = drug.name;
document.getElementById('editDrugDescription').value = drug.description || '';
document.getElementById('editModal').classList.add('show');
}
// Close edit modal
function closeEditModal() {
document.getElementById('editModal').classList.remove('show');
document.getElementById('editForm').reset();
}
// Show variants for a drug
function toggleDrugExpansion(drugId) {
if (expandedDrugs.has(drugId)) {
expandedDrugs.delete(drugId);
} else {
expandedDrugs.add(drugId);
}
renderDrugs();
}
// Open add variant modal
function openAddVariantModal(drugId) {
const drug = allDrugs.find(d => d.id === drugId);
if (!drug) return;
currentDrug = drug;
document.getElementById('variantDrugId').value = drug.id;
document.getElementById('addVariantModal').classList.add('show');
}
// Handle add variant form
async function handleAddVariant(e) {
e.preventDefault();
const drugId = parseInt(document.getElementById('variantDrugId').value);
const variantData = {
strength: document.getElementById('variantStrength').value,
quantity: parseFloat(document.getElementById('variantQuantity').value),
unit: document.getElementById('variantUnit').value,
low_stock_threshold: parseFloat(document.getElementById('variantThreshold').value)
};
try {
const response = await fetch(`${API_URL}/drugs/${drugId}/variants`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(variantData)
});
if (!response.ok) throw new Error('Failed to add variant');
document.getElementById('variantForm').reset();
closeModal(document.getElementById('addVariantModal'));
await loadDrugs();
renderDrugs();
alert('Variant added successfully!');
} catch (error) {
console.error('Error adding variant:', error);
alert('Failed to add variant. Check the console for details.');
}
}
// Open edit variant modal
function openEditVariantModal(variantId) {
// Find the variant from all drugs
let variant = null;
for (const drug of allDrugs) {
variant = drug.variants.find(v => v.id === variantId);
if (variant) break;
}
if (!variant) return;
document.getElementById('editVariantId').value = variant.id;
document.getElementById('editVariantStrength').value = variant.strength;
document.getElementById('editVariantQuantity').value = variant.quantity;
document.getElementById('editVariantUnit').value = variant.unit;
document.getElementById('editVariantThreshold').value = variant.low_stock_threshold;
document.getElementById('editVariantModal').classList.add('show');
}
// Handle edit variant form
async function handleEditVariant(e) {
e.preventDefault();
const variantId = parseInt(document.getElementById('editVariantId').value);
const variantData = {
strength: document.getElementById('editVariantStrength').value,
quantity: parseFloat(document.getElementById('editVariantQuantity').value),
unit: document.getElementById('editVariantUnit').value,
low_stock_threshold: parseFloat(document.getElementById('editVariantThreshold').value)
};
try {
const response = await fetch(`${API_URL}/variants/${variantId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(variantData)
});
if (!response.ok) throw new Error('Failed to update variant');
closeModal(document.getElementById('editVariantModal'));
await loadDrugs();
renderDrugs();
alert('Variant updated successfully!');
} catch (error) {
console.error('Error updating variant:', error);
alert('Failed to update variant. Check the console for details.');
}
}
// Dispense from variant
function dispenseVariant(variantId) {
// Update the dropdown display with all variants
updateDispenseDrugSelect();
// Pre-select the variant in the dispense modal
const drugSelect = document.getElementById('dispenseDrugSelect');
drugSelect.value = variantId;
// Open dispense modal
openModal(document.getElementById('dispenseModal'));
}
// Delete variant
async function deleteVariant(variantId) {
if (!confirm('Are you sure you want to delete this variant?')) return;
try {
const response = await fetch(`${API_URL}/variants/${variantId}`, {
method: 'DELETE'
});
if (!response.ok) throw new Error('Failed to delete variant');
await loadDrugs();
renderDrugs();
alert('Variant deleted successfully!');
} catch (error) {
console.error('Error deleting variant:', error);
alert('Failed to delete variant. Check the console for details.');
}
}
// Show dispensing history for a drug
async function showDrugHistory(drugId) {
const drug = allDrugs.find(d => d.id === drugId);
if (!drug) return;
const historyModal = document.getElementById('historyModal');
const historyContent = document.getElementById('historyContent');
document.getElementById('historyDrugName').textContent = drug.name;
historyContent.innerHTML = 'Loading history...
';
openModal(historyModal);
try {
const response = await fetch(`${API_URL}/dispense/history`);
if (!response.ok) throw new Error('Failed to fetch history');
const allHistory = await response.json();
// Filter history for this drug's variants
const variantIds = drug.variants.map(v => v.id);
const drugHistory = allHistory.filter(item => variantIds.includes(item.drug_variant_id));
if (drugHistory.length === 0) {
historyContent.innerHTML = 'No dispensing history for this drug.
';
return;
}
// Sort by dispensed_at descending (most recent first)
drugHistory.sort((a, b) => new Date(b.dispensed_at) - new Date(a.dispensed_at));
const historyHtml = drugHistory.map(item => {
const variant = drug.variants.find(v => v.id === item.drug_variant_id);
const date = new Date(item.dispensed_at).toLocaleDateString();
const time = new Date(item.dispensed_at).toLocaleTimeString();
return `
Quantity:
${item.quantity} ${variant.unit}
Animal:
${escapeHtml(item.animal_name)}
User:
${escapeHtml(item.user_name)}
${item.notes ? `
Notes:
${escapeHtml(item.notes)}
` : ''}
`;
}).join('');
historyContent.innerHTML = historyHtml;
} catch (error) {
console.error('Error fetching history:', error);
historyContent.innerHTML = 'Failed to load history. Check the console for details.
';
}
}
// Handle edit drug form
async function handleEditDrug(e) {
e.preventDefault();
const drugId = parseInt(document.getElementById('editDrugId').value);
const drugData = {
name: document.getElementById('editDrugName').value,
description: document.getElementById('editDrugDescription').value
};
try {
const response = await fetch(`${API_URL}/drugs/${drugId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(drugData)
});
if (!response.ok) throw new Error('Failed to update drug');
closeEditModal();
await loadDrugs();
alert('Drug updated successfully!');
} catch (error) {
console.error('Error updating drug:', error);
alert('Failed to update drug. Check the console for details.');
}
}
// Delete drug
async function deleteDrug(drugId) {
if (!confirm('Are you sure you want to delete this drug?')) return;
try {
const response = await fetch(`${API_URL}/drugs/${drugId}`, {
method: 'DELETE'
});
if (!response.ok) throw new Error('Failed to delete drug');
await loadDrugs();
alert('Drug deleted successfully!');
} catch (error) {
console.error('Error deleting drug:', error);
alert('Failed to delete drug. Check the console for details.');
}
}
// Update filter button states
function updateFilterButtons() {
document.getElementById('showAllBtn').classList.toggle('active', !showLowStockOnly);
document.getElementById('showLowStockBtn').classList.toggle('active', showLowStockOnly);
}
// Escape HTML to prevent XSS
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}