const API_URL = '/api'; let accessToken = null; let currentUser = null; let allDrugs = []; let auditTrailRows = []; let dispensingRows = []; let globalInventoryRows = []; let batchAttentionRows = []; let activeReportType = 'dispensing'; const batchLookupById = new Map(); const loadedBatchVariants = new Set(); function openModal(modal) { if (!modal) return; modal.classList.add('show'); document.body.style.overflow = 'hidden'; } function closeModal(modal) { if (!modal) return; modal.classList.remove('show'); document.body.style.overflow = 'auto'; } function resetDisposeBatchModal() { const form = document.getElementById('disposeBatchForm'); if (form) { form.reset(); } const batchIdInput = document.getElementById('disposeBatchId'); const batchNameInput = document.getElementById('disposeBatchName'); if (batchIdInput) batchIdInput.value = ''; if (batchNameInput) batchNameInput.value = ''; } function closeDisposeBatchModal() { resetDisposeBatchModal(); closeModal(document.getElementById('disposeBatchModal')); } 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); setTimeout(() => { toast.classList.add('fade-out'); setTimeout(() => { if (container.contains(toast)) container.removeChild(toast); }, 300); }, duration); } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function getVariantInfoById(variantId) { for (const drug of allDrugs) { const variant = drug.variants.find(v => v.id === variantId); if (variant) { return { drugName: drug.name, strength: variant.strength, unit: variant.unit }; } } return { drugName: 'Unknown Drug', strength: '', unit: 'units' }; } function extractDrugLabelFromAuditRow(row) { const details = row.details || {}; if (details.drug_name) return details.drug_name; if (details.before?.name) return details.before.name; if (details.after?.name) return details.after.name; if (details.name && row.entity_type === 'drug') return details.name; if (details.drug_id) { const info = getVariantInfoById(details.drug_id); if (info.drugName !== 'Unknown Drug') return info.drugName; } if (details.variant_id) { const info = getVariantInfoById(details.variant_id); if (info.drugName) return `${info.drugName}${info.strength ? ` ${info.strength}` : ''}`; } if (row.entity_type === 'variant' && row.entity_id) { const info = getVariantInfoById(row.entity_id); if (info.drugName) return `${info.drugName}${info.strength ? ` ${info.strength}` : ''}`; } return 'N/A'; } function extractDrugLabelFromDispenseRow(row) { const info = getVariantInfoById(row.drug_variant_id); return `${info.drugName}${info.strength ? ` ${info.strength}` : ''}`; } function formatAuditSummary(row) { const details = row.details || {}; if (row.action === 'dispense.create') { const qty = details.quantity || details.dispensed_quantity || ''; const animal = details.animal_name ? ` for ${details.animal_name}` : ''; return `Dispensed ${qty}${animal}`.trim(); } if (row.action === 'batch.create' || row.action === 'batch.update') { const batch = details.batch_number || details.after?.batch_number || details.before?.batch_number || ''; const quantity = details.quantity || details.after?.quantity || ''; return `Batch ${batch}${quantity !== '' ? ` (qty ${quantity})` : ''}`.trim(); } if (row.action === 'drug.create' || row.action === 'drug.update') { const name = details.name || details.after?.name || details.before?.name || extractDrugLabelFromAuditRow(row); return `Drug ${name}`; } if (row.action === 'variant.create' || row.action === 'variant.update') { const variant = details.strength || details.after?.strength || details.before?.strength || ''; const drug = extractDrugLabelFromAuditRow(row); return `Variant ${variant}${drug !== 'N/A' ? ` (${drug})` : ''}`.trim(); } if (details.message) return String(details.message); return row.action || 'Event'; } function formatDispenseAllocation(row) { if (row.allocations && row.allocations.length > 0) { return row.allocations .map(a => { const batch = batchLookupById.get(a.batch_id); if (batch) { const expiry = batch.expiry_date ? new Date(batch.expiry_date).toLocaleDateString() : 'Unknown'; return `${batch.batch_number} (exp ${expiry}): ${a.quantity}`; } return `Batch ${a.batch_id}: ${a.quantity}`; }) .join(', '); } if (row.batch_id) { const batch = batchLookupById.get(row.batch_id); if (batch) { const expiry = batch.expiry_date ? new Date(batch.expiry_date).toLocaleDateString() : 'Unknown'; return `${batch.batch_number} (exp ${expiry})`; } return `Batch ${row.batch_id}`; } return 'N/A'; } async function ensureBatchLookupForDispensing(rows) { const variantIds = Array.from(new Set(rows.map(row => row.drug_variant_id).filter(Boolean))); for (const variantId of variantIds) { if (loadedBatchVariants.has(variantId)) { continue; } try { const response = await apiCall(`/variants/${variantId}/batches`); if (!response.ok) { continue; } const batches = await response.json(); batches.forEach(batch => { batchLookupById.set(batch.id, { batch_number: batch.batch_number, expiry_date: batch.expiry_date }); }); loadedBatchVariants.add(variantId); } catch (error) { console.error(`Failed to load batch lookup for variant ${variantId}:`, error); } } } function detailsContainsText(details, searchText) { if (!details) return false; try { return JSON.stringify(details).toLowerCase().includes(searchText); } catch { return false; } } function getActiveRows() { if (activeReportType === 'dispensing') return dispensingRows; if (activeReportType === 'global_inventory') return globalInventoryRows; if (activeReportType === 'batch_attention') return batchAttentionRows; return auditTrailRows; } function getRowUser(row) { if (activeReportType === 'dispensing') return row.user_name || 'unknown'; if (activeReportType === 'global_inventory') return ''; if (activeReportType === 'batch_attention') return ''; return row.actor_username || 'system'; } function getRowDrug(row) { if (activeReportType === 'dispensing') return extractDrugLabelFromDispenseRow(row); if (activeReportType === 'global_inventory') return `${row.drug_name || 'Unknown Drug'}${row.strength ? ` ${row.strength}` : ''}`; if (activeReportType === 'batch_attention') return `${row.drug_name || 'Unknown Drug'}${row.strength ? ` ${row.strength}` : ''}`; return extractDrugLabelFromAuditRow(row); } function getRowDate(row) { if (activeReportType === 'dispensing') return new Date(row.dispensed_at); if (activeReportType === 'global_inventory') return row.expiry_date ? new Date(row.expiry_date) : null; if (activeReportType === 'batch_attention') return new Date(row.expiry_date); return new Date(row.created_at); } function populateCommonFilters(rows) { const userFilter = document.getElementById('reportUserFilter'); const drugFilter = document.getElementById('reportDrugFilter'); if (!userFilter || !drugFilter) return; const previousUser = userFilter.value; const previousDrug = drugFilter.value; const users = Array.from(new Set(rows.map(getRowUser).filter(Boolean))).sort((a, b) => a.localeCompare(b)); const drugs = Array.from(new Set(rows.map(getRowDrug).filter(label => label && label !== 'N/A'))).sort((a, b) => a.localeCompare(b)); userFilter.innerHTML = ''; users.forEach(user => { const option = document.createElement('option'); option.value = user; option.textContent = user; userFilter.appendChild(option); }); drugFilter.innerHTML = ''; drugs.forEach(drug => { const option = document.createElement('option'); option.value = drug; option.textContent = drug; drugFilter.appendChild(option); }); userFilter.value = users.includes(previousUser) ? previousUser : ''; drugFilter.value = drugs.includes(previousDrug) ? previousDrug : ''; } function renderAuditTable(rows) { const container = document.getElementById('reportsTableContainer'); if (!container) return; if (!rows.length) { container.innerHTML = '

No audit events match the selected filters.

'; return; } const rowsHtml = rows.map(row => { const dateText = new Date(row.created_at).toLocaleString(); const userText = row.actor_username || 'system'; const detailsText = row.details ? escapeHtml(JSON.stringify(row.details, null, 2)) : '-'; return ` ${escapeHtml(dateText)} ${escapeHtml(userText)} ${escapeHtml(row.action || '')} ${escapeHtml(row.entity_type || '')} ${escapeHtml(extractDrugLabelFromAuditRow(row))} ${escapeHtml(formatAuditSummary(row))} ${detailsText} `; }).join(''); container.innerHTML = ` ${rowsHtml}
Date User Action Entity Drug Summary Details
`; } function renderDispensingTable(rows) { const container = document.getElementById('reportsTableContainer'); if (!container) return; if (!rows.length) { container.innerHTML = '

No dispensing records match the selected filters.

'; return; } const rowsHtml = rows.map(row => { const dateText = new Date(row.dispensed_at).toLocaleString(); const info = getVariantInfoById(row.drug_variant_id); const quantityText = `${row.quantity} ${info.unit || 'units'}`; const animal = row.animal_name || '-'; const vet = row.prescribing_vet || '-'; const notes = row.notes || '-'; const allocations = formatDispenseAllocation(row); return ` ${escapeHtml(dateText)} ${escapeHtml(row.user_name || 'unknown')} ${escapeHtml(info.drugName)} ${escapeHtml(info.strength || '-')} ${escapeHtml(quantityText)} ${escapeHtml(animal)} ${escapeHtml(vet)} ${escapeHtml(allocations)} ${escapeHtml(notes)} `; }).join(''); container.innerHTML = ` ${rowsHtml}
Date User Drug Strength Quantity Animal Prescribing Vet Batch Allocation Notes
`; } function renderGlobalInventoryTable(rows) { const container = document.getElementById('reportsTableContainer'); if (!container) return; if (!rows.length) { container.innerHTML = '

No inventory lines match the selected filters.

'; return; } const rowsHtml = rows.map(row => { const expiryText = row.expiry_date ? new Date(row.expiry_date).toLocaleDateString() : '-'; const quantityText = `${row.quantity} ${row.unit || 'units'}`; const batchText = row.inventory_source === 'legacy' ? 'Legacy stock' : (row.batch_number || ''); const locationText = row.location_name || '-'; return ` ${escapeHtml(row.drug_name || '')} ${escapeHtml(row.strength || '-')} ${escapeHtml(batchText)} ${escapeHtml(quantityText)} ${escapeHtml(locationText)} ${escapeHtml(expiryText)} `; }).join(''); container.innerHTML = ` ${rowsHtml}
Drug Variant Batch Quantity Location Expiry
`; } function renderBatchAttentionTable(rows) { const container = document.getElementById('reportsTableContainer'); if (!container) return; if (!rows.length) { container.innerHTML = '

No expired batches match the selected filters.

'; return; } const rowsHtml = rows.map(row => { const expiryText = row.expiry_date ? new Date(row.expiry_date).toLocaleDateString() : 'Unknown'; const quantityText = `${row.quantity} ${row.unit || 'units'}`; const statusText = 'Expired'; const isExpired = true; const packState = row.current_loose_base_units > 0 ? `${row.current_full_pack_count || 0} full packs + ${row.current_loose_base_units} loose ${row.unit || 'units'}` : `${row.current_full_pack_count || 0} full packs`; return ` ${escapeHtml(row.drug_name || '')} ${escapeHtml(row.strength || '-')} ${escapeHtml(row.batch_number || '')} ${escapeHtml(quantityText)} ${escapeHtml(packState)} ${escapeHtml(row.location || '-')} ${escapeHtml(expiryText)} ${escapeHtml(statusText)} ${isExpired ? `` : '-'} `; }).join(''); container.innerHTML = ` ${rowsHtml}
Drug Strength Batch Quantity Pack State Location Expiry Status Action
`; } function disposeBatchFromReport(batchId, batchNumber) { const modal = document.getElementById('disposeBatchModal'); const batchIdInput = document.getElementById('disposeBatchId'); const batchNameInput = document.getElementById('disposeBatchName'); const notesInput = document.getElementById('disposeBatchNotes'); if (!modal || !batchIdInput || !batchNameInput || !notesInput) { showToast('Dispose batch modal is unavailable.', 'error'); return; } batchIdInput.value = String(batchId); batchNameInput.value = batchNumber; notesInput.value = ''; openModal(modal); } async function handleDisposeBatch(e) { e.preventDefault(); const batchId = parseInt(document.getElementById('disposeBatchId')?.value || '', 10); const notes = document.getElementById('disposeBatchNotes')?.value.trim() || ''; if (!batchId) { showToast('Batch disposal context is unavailable.', 'error'); return; } try { const response = await apiCall(`/batches/${batchId}/dispose`, { method: 'POST', body: JSON.stringify({ notes: notes || null }) }); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to dispose batch'); } closeDisposeBatchModal(); await loadActiveReport(); showToast('Expired batch marked as disposed.', 'success'); } catch (error) { console.error('Error disposing batch from report:', error); showToast('Failed to dispose batch: ' + error.message, 'error'); } } function applyCurrentFilters() { const userFilter = document.getElementById('reportUserFilter'); const drugFilter = document.getElementById('reportDrugFilter'); const fromDateInput = document.getElementById('reportFromDate'); const toDateInput = document.getElementById('reportToDate'); const searchInput = document.getElementById('reportActionSearch'); const reportsSummary = document.getElementById('reportsSummary'); const selectedUser = userFilter ? userFilter.value : ''; const selectedDrug = drugFilter ? drugFilter.value : ''; const fromDate = fromDateInput && fromDateInput.value ? new Date(`${fromDateInput.value}T00:00:00`) : null; const toDate = toDateInput && toDateInput.value ? new Date(`${toDateInput.value}T23:59:59`) : null; const searchText = searchInput ? searchInput.value.trim().toLowerCase() : ''; const sourceRows = getActiveRows(); const filteredRows = sourceRows.filter(row => { const userMatch = !selectedUser || getRowUser(row) === selectedUser; const drugMatch = !selectedDrug || getRowDrug(row) === selectedDrug; const rowDate = getRowDate(row); const fromMatch = !fromDate || !rowDate || rowDate >= fromDate; const toMatch = !toDate || !rowDate || rowDate <= toDate; let textMatch = true; if (searchText) { if (activeReportType === 'dispensing') { const info = getVariantInfoById(row.drug_variant_id); const haystack = [ row.user_name || '', info.drugName || '', info.strength || '', row.animal_name || '', row.prescribing_vet || '', row.notes || '', formatDispenseAllocation(row) ].join(' ').toLowerCase(); textMatch = haystack.includes(searchText); } else if (activeReportType === 'global_inventory') { const haystack = [ row.drug_name || '', row.strength || '', row.batch_number || '', row.inventory_source || '', row.location_name || '', row.unit || '' ].join(' ').toLowerCase(); textMatch = haystack.includes(searchText); } else if (activeReportType === 'batch_attention') { const haystack = [ row.drug_name || '', row.strength || '', row.batch_number || '', row.location || '', row.status || '', row.unit || '' ].join(' ').toLowerCase(); textMatch = haystack.includes(searchText); } else { const actionText = (row.action || '').toLowerCase(); const entityText = (row.entity_type || '').toLowerCase(); const summaryText = formatAuditSummary(row).toLowerCase(); textMatch = actionText.includes(searchText) || entityText.includes(searchText) || summaryText.includes(searchText) || detailsContainsText(row.details, searchText); } } return userMatch && drugMatch && fromMatch && toMatch && textMatch; }); if (reportsSummary) { const reportName = activeReportType === 'dispensing' ? 'dispensing records' : activeReportType === 'global_inventory' ? 'inventory lines' : activeReportType === 'batch_attention' ? 'expired batches' : 'audit events'; reportsSummary.textContent = `Showing ${filteredRows.length} of ${sourceRows.length} ${reportName}`; } if (activeReportType === 'dispensing') { renderDispensingTable(filteredRows); } else if (activeReportType === 'global_inventory') { renderGlobalInventoryTable(filteredRows); } else if (activeReportType === 'batch_attention') { renderBatchAttentionTable(filteredRows); } else { renderAuditTable(filteredRows); } return filteredRows; } function updateReportHeading() { const heading = document.getElementById('reportsHeading'); const searchInput = document.getElementById('reportActionSearch'); const userFilter = document.getElementById('reportUserFilter')?.closest('.report-control'); const stockCheckPdfBtn = document.getElementById('stockCheckPdfBtn'); if (!heading || !searchInput) return; if (activeReportType === 'dispensing') { heading.textContent = 'Dispensing History'; searchInput.placeholder = 'Search user, drug, animal, notes, batch allocation...'; if (userFilter) userFilter.style.display = ''; if (stockCheckPdfBtn) stockCheckPdfBtn.style.display = 'none'; } else if (activeReportType === 'global_inventory') { heading.textContent = 'Global Inventory'; searchInput.placeholder = 'Search drug, variant, batch, location...'; if (userFilter) userFilter.style.display = 'none'; if (stockCheckPdfBtn) stockCheckPdfBtn.style.display = ''; } else if (activeReportType === 'batch_attention') { heading.textContent = 'Expired Batches'; searchInput.placeholder = 'Search drug, batch, location...'; if (userFilter) userFilter.style.display = 'none'; if (stockCheckPdfBtn) stockCheckPdfBtn.style.display = 'none'; } else { heading.textContent = 'Audit Trail (Raw)'; searchInput.placeholder = 'Search action, entity, details...'; if (userFilter) userFilter.style.display = ''; if (stockCheckPdfBtn) stockCheckPdfBtn.style.display = 'none'; } } function formatDisplayDate(value) { if (!value) return '-'; const date = typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value) ? new Date(`${value}T00:00:00`) : new Date(value); return Number.isNaN(date.getTime()) ? '-' : date.toLocaleDateString(); } function getReportFilterSummary() { const drugFilter = document.getElementById('reportDrugFilter')?.value || ''; const fromDate = document.getElementById('reportFromDate')?.value || ''; const toDate = document.getElementById('reportToDate')?.value || ''; const searchText = document.getElementById('reportActionSearch')?.value.trim() || ''; const parts = []; if (drugFilter) parts.push(`Drug: ${drugFilter}`); if (fromDate) parts.push(`From: ${formatDisplayDate(fromDate)}`); if (toDate) parts.push(`To: ${formatDisplayDate(toDate)}`); if (searchText) parts.push(`Search: ${searchText}`); return parts.length ? parts.join(' | ') : 'All inventory lines'; } function getStockCheckRows() { const previousReportType = activeReportType; activeReportType = 'global_inventory'; const rows = applyCurrentFilters() || []; activeReportType = previousReportType; return rows; } function buildStockCheckPdfHtml(rows) { const groupedRows = new Map(); const sortedRows = [...rows].sort((a, b) => { const drugCompare = String(a.drug_name || '').localeCompare(String(b.drug_name || ''), undefined, { sensitivity: 'base' }); if (drugCompare !== 0) return drugCompare; const strengthCompare = String(a.strength || '').localeCompare(String(b.strength || ''), undefined, { numeric: true, sensitivity: 'base' }); if (strengthCompare !== 0) return strengthCompare; const expiryCompare = String(a.expiry_date || '9999-12-31').localeCompare(String(b.expiry_date || '9999-12-31')); if (expiryCompare !== 0) return expiryCompare; return String(a.batch_number || '').localeCompare(String(b.batch_number || ''), undefined, { numeric: true, sensitivity: 'base' }); }); sortedRows.forEach(row => { const drugName = row.drug_name || 'Unknown Drug'; if (!groupedRows.has(drugName)) groupedRows.set(drugName, []); groupedRows.get(drugName).push(row); }); const generatedAt = new Date().toLocaleString(); const filterSummary = getReportFilterSummary(); const totalLines = rows.length; const sectionsHtml = Array.from(groupedRows.entries()).map(([drugName, drugRows]) => { const bodyHtml = drugRows.map(row => { const batchText = row.inventory_source === 'legacy' ? 'Legacy stock' : (row.batch_number || '-'); const quantityText = `${row.quantity ?? 0} ${row.unit || 'units'}`; const controlledText = row.is_controlled ? 'Yes' : 'No'; return ` ${escapeHtml(row.strength || '-')} ${escapeHtml(batchText)} ${escapeHtml(quantityText)} ${escapeHtml(row.location_name || '-')} ${escapeHtml(formatDisplayDate(row.expiry_date))} ${escapeHtml(controlledText)} `; }).join(''); return `

${escapeHtml(drugName)}

${bodyHtml}
Variant Batch System Qty Location Expiry CD Actual Notes
`; }).join(''); return ` Manual Stock Check

Manual Stock Check

Generated: ${escapeHtml(generatedAt)}
Filters: ${escapeHtml(filterSummary)}
Inventory lines: ${escapeHtml(String(totalLines))}
Checked by
Date
${sectionsHtml || '
No inventory lines match the selected filters.
'} `; } function generateStockCheckPdf() { if (activeReportType !== 'global_inventory') { showToast('Select Global Inventory to generate a stock check PDF.', 'warning'); return; } const rows = getStockCheckRows(); if (!rows.length) { showToast('No inventory lines match the selected filters.', 'warning'); return; } const printWindow = window.open('', '_blank'); if (!printWindow) { showToast('Allow pop-ups to generate the stock check PDF.', 'warning'); return; } printWindow.document.open(); printWindow.document.write(buildStockCheckPdfHtml(rows)); printWindow.document.close(); } async function apiCall(endpoint, options = {}) { const headers = { 'Content-Type': 'application/json', ...options.headers }; if (accessToken) headers.Authorization = `Bearer ${accessToken}`; const response = await fetch(`${API_URL}${endpoint}`, { ...options, headers }); if (response.status === 401) { localStorage.removeItem('accessToken'); localStorage.removeItem('currentUser'); window.location.href = 'index.html'; throw new Error('Authentication expired'); } return response; } async function loadReferenceData() { try { const drugResponse = await apiCall('/drugs'); if (drugResponse.ok) { allDrugs = await drugResponse.json(); } } catch (error) { console.error('Failed to load drug reference data:', error); } } async function loadActiveReport() { const container = document.getElementById('reportsTableContainer'); const reportsSummary = document.getElementById('reportsSummary'); if (container) { const loadingText = activeReportType === 'dispensing' ? 'Loading dispensing history...' : activeReportType === 'global_inventory' ? 'Loading global inventory...' : activeReportType === 'batch_attention' ? 'Loading expired batches...' : 'Loading audit trail...'; container.innerHTML = `

${loadingText}

`; } if (reportsSummary) reportsSummary.textContent = ''; try { if (activeReportType === 'dispensing') { const response = await apiCall('/dispense/history?limit=1000'); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to load dispensing history'); } dispensingRows = await response.json(); await ensureBatchLookupForDispensing(dispensingRows); populateCommonFilters(dispensingRows); } else if (activeReportType === 'global_inventory') { const response = await apiCall('/reports/global-inventory'); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to load global inventory'); } globalInventoryRows = await response.json(); populateCommonFilters(globalInventoryRows); } else if (activeReportType === 'batch_attention') { const response = await apiCall('/reports/batch-attention'); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to load batch attention report'); } batchAttentionRows = await response.json(); populateCommonFilters(batchAttentionRows); } else { const response = await apiCall('/reports/audit-trail'); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to load audit trail report'); } auditTrailRows = await response.json(); populateCommonFilters(auditTrailRows); } applyCurrentFilters(); } catch (error) { console.error('Error loading report:', error); if (container) { container.innerHTML = `

Failed to load report: ${escapeHtml(error.message)}

`; } showToast(`Failed to load report: ${error.message}`, 'error'); } } function showReportsPage() { document.getElementById('reportsApp').style.display = 'block'; document.getElementById('reportsErrorState').style.display = 'none'; const userDisplay = document.getElementById('reportsCurrentUser'); if (userDisplay && currentUser) { const roleLabel = currentUser.role.charAt(0).toUpperCase() + currentUser.role.slice(1); userDisplay.textContent = `👤 ${currentUser.username} [${roleLabel}]`; } } function showErrorState(message) { const errorMessage = document.getElementById('reportsErrorMessage'); if (errorMessage) errorMessage.textContent = message; document.getElementById('reportsApp').style.display = 'none'; document.getElementById('reportsErrorState').style.display = 'flex'; } function setupEventListeners() { const reportTypeSelect = document.getElementById('reportTypeSelect'); const applyBtn = document.getElementById('applyReportFiltersBtn'); const clearBtn = document.getElementById('clearReportFiltersBtn'); const refreshBtn = document.getElementById('refreshReportsBtn'); const stockCheckPdfBtn = document.getElementById('stockCheckPdfBtn'); const backBtn = document.getElementById('backToInventoryBtn'); const logoutBtn = document.getElementById('reportsLogoutBtn'); const goToLoginBtn = document.getElementById('goToLoginBtn'); const disposeBatchForm = document.getElementById('disposeBatchForm'); const cancelDisposeBatchBtn = document.getElementById('cancelDisposeBatchBtn'); const closeButtons = document.querySelectorAll('.close'); const userFilter = document.getElementById('reportUserFilter'); const drugFilter = document.getElementById('reportDrugFilter'); const fromDate = document.getElementById('reportFromDate'); const toDate = document.getElementById('reportToDate'); const searchInput = document.getElementById('reportActionSearch'); if (reportTypeSelect) { reportTypeSelect.addEventListener('change', async (e) => { activeReportType = e.target.value; updateReportHeading(); await loadActiveReport(); }); } if (applyBtn) applyBtn.addEventListener('click', applyCurrentFilters); if (refreshBtn) refreshBtn.addEventListener('click', loadActiveReport); if (stockCheckPdfBtn) stockCheckPdfBtn.addEventListener('click', generateStockCheckPdf); if (clearBtn) { clearBtn.addEventListener('click', () => { if (userFilter) userFilter.value = ''; if (drugFilter) drugFilter.value = ''; if (fromDate) fromDate.value = ''; if (toDate) toDate.value = ''; if (searchInput) searchInput.value = ''; applyCurrentFilters(); }); } if (userFilter) userFilter.addEventListener('change', applyCurrentFilters); if (drugFilter) drugFilter.addEventListener('change', applyCurrentFilters); if (fromDate) fromDate.addEventListener('change', applyCurrentFilters); if (toDate) toDate.addEventListener('change', applyCurrentFilters); if (searchInput) { let timeout; searchInput.addEventListener('input', () => { clearTimeout(timeout); timeout = setTimeout(applyCurrentFilters, 120); }); } if (backBtn) backBtn.addEventListener('click', () => { window.location.href = 'index.html'; }); if (logoutBtn) logoutBtn.addEventListener('click', () => { localStorage.removeItem('accessToken'); localStorage.removeItem('currentUser'); window.location.href = 'index.html'; }); if (goToLoginBtn) goToLoginBtn.addEventListener('click', () => { window.location.href = 'index.html'; }); if (disposeBatchForm) disposeBatchForm.addEventListener('submit', handleDisposeBatch); if (cancelDisposeBatchBtn) cancelDisposeBatchBtn.addEventListener('click', closeDisposeBatchModal); closeButtons.forEach(btn => btn.addEventListener('click', (e) => { const modal = e.target.closest('.modal'); if (modal?.id === 'disposeBatchModal') { closeDisposeBatchModal(); return; } closeModal(modal); })); window.addEventListener('click', (e) => { if (e.target.classList.contains('modal')) { if (e.target.id === 'disposeBatchModal') { closeDisposeBatchModal(); return; } closeModal(e.target); } }); } async function initializeReportsPage() { const token = localStorage.getItem('accessToken'); const userData = localStorage.getItem('currentUser'); if (!token || !userData) { showErrorState('You are not logged in. Please sign in first.'); return; } accessToken = token; try { currentUser = JSON.parse(userData); } catch { showErrorState('Invalid session data. Please sign in again.'); return; } if (!currentUser.role && currentUser.is_admin !== undefined) { currentUser.role = currentUser.is_admin ? 'admin' : 'user'; } if (currentUser.role !== 'admin') { showErrorState('Only admin users can access reports.'); return; } setupEventListeners(); showReportsPage(); updateReportHeading(); await loadReferenceData(); await loadActiveReport(); } document.addEventListener('DOMContentLoaded', initializeReportsPage);