document.addEventListener('alpine:init', () => { Alpine.data("validatePage", () => ({ // validate page specific unvalidatedList: [], isLoading: false, isValidating: false, // Date filter - missing in original code! filter: { date1: "", date2: "" }, filterTable: "", // Sorting & Pagination (shared with dashboard) sortCol: 'REQDATE', sortAsc: false, currentPage: 1, pageSize: 30, // Cached computed properties to prevent memory leak unvalidatedFiltered: [], unvalidatedSorted: [], unvalidatedPaginated: [], unvalidatedTotalPages: 1, unvalidatedCount: 0, sort(col) { if (this.sortCol === col) { this.sortAsc = !this.sortAsc; } else { this.sortCol = col; this.sortAsc = true; } this.computeUnvalidatedSorted(); this.computeUnvalidatedPaginated(); }, nextPage() { if (this.currentPage < this.unvalidatedTotalPages) { this.currentPage++; this.computeUnvalidatedPaginated(); } }, prevPage() { if (this.currentPage > 1) { this.currentPage--; this.computeUnvalidatedPaginated(); } }, setFilterTable(value) { this.filterTable = value; this.currentPage = 1; this.computeUnvalidatedFiltered(); this.computeUnvalidatedSorted(); this.computeUnvalidatedTotalPages(); this.computeUnvalidatedPaginated(); }, // Compute methods - called only when dependencies change computeUnvalidatedFiltered() { if (!this.filterTable) { this.unvalidatedFiltered = this.unvalidatedList; } else { const searchTerm = this.filterTable.toLowerCase(); this.unvalidatedFiltered = this.unvalidatedList.filter(item => Object.values(item).some(value => String(value).toLowerCase().includes(searchTerm) ) ); } }, computeUnvalidatedSorted() { this.unvalidatedSorted = this.unvalidatedFiltered.slice().sort((a, b) => { let modifier = this.sortAsc ? 1 : -1; if (a[this.sortCol] < b[this.sortCol]) return -1 * modifier; if (a[this.sortCol] > b[this.sortCol]) return 1 * modifier; return 0; }); }, computeUnvalidatedPaginated() { const start = (this.currentPage - 1) * this.pageSize; const end = start + this.pageSize; this.unvalidatedPaginated = this.unvalidatedSorted.slice(start, end); }, computeUnvalidatedTotalPages() { this.unvalidatedTotalPages = Math.ceil(this.unvalidatedFiltered.length / this.pageSize) || 1; }, computeUnvalidatedCount() { this.unvalidatedCount = this.unvalidatedList.length; }, init() { this.today = new Date().toISOString().slice(0, 10); this.filter.date1 = this.today; this.filter.date2 = this.today; // Initial load only - no auto-refresh this.fetchUnvalidated(); // Keyboard shortcuts - added/removed when dialog opens/closes this._keyboardHandler = (e) => { if (this.isDialogValOpen) { if (e.key === 'n' || e.key === 'N') { if (!e.target.closest('input, textarea, button')) { e.preventDefault(); this.skipToNext(); } } } }; document.addEventListener('keydown', this._keyboardHandler); }, fetchUnvalidated() { this.isLoading = true; this.unvalidatedList = []; let param = new URLSearchParams(this.filter).toString(); fetch(`${BASEURL}/api/validate/unvalidated?${param}`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }).then(res => res.json()).then(data => { this.unvalidatedList = data.data ?? []; // Compute derived data after list is loaded this.computeUnvalidatedFiltered(); this.computeUnvalidatedCount(); this.computeUnvalidatedSorted(); this.computeUnvalidatedTotalPages(); this.computeUnvalidatedPaginated(); }).finally(() => { this.isLoading = false; }); }, resetUnvalidated() { this.filter.date1 = this.today; this.filter.date2 = this.today; this.fetchUnvalidated(); }, /* * validate dialog methods */ valAccessnumber: null, valItem: null, currentIndex: 0, isDialogValOpen: false, isIframeLoaded: false, validationDelayRemaining: 0, validationTimer: null, toast: { show: false, message: '', type: 'success' }, showToast(message, type = 'success') { this.toast = { show: true, message, type }; setTimeout(() => { this.toast.show = false; }, 2000); }, onIframeLoad() { this.isIframeLoaded = true; this.validationDelayRemaining = 1; this.validationTimer = setInterval(() => { this.validationDelayRemaining--; if (this.validationDelayRemaining <= 0) { clearInterval(this.validationTimer); this.validationTimer = null; } }, 1000); }, openValDialogByIndex(index) { const filtered = this.unvalidatedFiltered; if (index < 0 || index >= filtered.length) { this.showToast('No more requests', 'error'); this.closeValDialog(); return; } // Reset iframe loading state this.isIframeLoaded = false; this.validationDelayRemaining = 0; if (this.validationTimer) { clearInterval(this.validationTimer); this.validationTimer = null; } const item = filtered[index]; this.currentIndex = index; this.valAccessnumber = item.SP_ACCESSNUMBER; this.valItem = item; this.isDialogValOpen = true; // Re-add keyboard handler when dialog opens if (this._keyboardHandler) { document.addEventListener('keydown', this._keyboardHandler); } setTimeout(() => { const btn = document.getElementById('validate-btn'); if (btn) btn.focus(); }, 50); }, openValDialog(accessnumber) { // Find index by accessnumber const filtered = this.unvalidatedFiltered; const index = filtered.findIndex(item => item.SP_ACCESSNUMBER === accessnumber); if (index !== -1) { this.openValDialogByIndex(index); } else { this.openValDialogByIndex(0); } }, closeValDialog() { this.isDialogValOpen = false; this.valAccessnumber = null; this.valItem = null; if (this.validationTimer) { clearInterval(this.validationTimer); this.validationTimer = null; } this.validationDelayRemaining = 0; this.isIframeLoaded = false; // Remove keyboard handler when dialog closes if (this._keyboardHandler) { document.removeEventListener('keydown', this._keyboardHandler); this._keyboardHandler = null; } }, skipToNext() { const nextIndex = (this.currentIndex + 1) % this.unvalidatedFiltered.length; this.closeValDialog(); // Use setTimeout for reliable focus after dialog re-renders setTimeout(() => this.openValDialogByIndex(nextIndex), 50); }, validate(accessnumber, userid) { if (!this.isIframeLoaded || this.validationDelayRemaining > 0) { this.showToast('Please wait for the report to load', 'error'); return; } if (!confirm(`Validate request ${accessnumber}?`)) return; this.isValidating = true; fetch(`${BASEURL}/api/requests/validate/${accessnumber}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userid: `${userid}` }) }).then(response => response.json()).then(data => { console.log(data); if (data.val === 2) { this.showToast(`Validated (val2): ${accessnumber} - PDF queued`); // Trigger PDF auto-generation after val2 fetch(`${BASEURL}/report/${accessnumber}/pdf`).then(res => res.json()).then(pdfData => { if (pdfData.success) { console.log('PDF generation queued:', pdfData.jobId); this.showToast(`${pdfData.lang} PDF queued for generation`, 'success'); } else { console.error('PDF generation failed:', pdfData.error); this.showToast('PDF generation failed', 'error'); } }).catch(err => { console.error('PDF generation request failed:', err); this.showToast('PDF generation failed', 'error'); }); } else { this.showToast(`Validated: ${accessnumber}`); } this.unvalidatedList = this.unvalidatedList.filter( item => item.SP_ACCESSNUMBER !== accessnumber ); this.computeUnvalidatedFiltered(); this.computeUnvalidatedSorted(); this.computeUnvalidatedTotalPages(); if (this.currentPage > this.unvalidatedTotalPages) { this.currentPage = this.unvalidatedTotalPages; } this.computeUnvalidatedPaginated(); const filteredLength = this.unvalidatedFiltered.length; if (filteredLength > 0 && this.currentIndex < filteredLength) { const nextIndex = this.currentIndex; this.closeValDialog(); setTimeout(() => this.openValDialogByIndex(nextIndex), 50); } else { this.closeValDialog(); this.showToast('All requests validated!'); } if (data.message && data.message.includes('already validate')) { alert(data.message); } }).catch(() => { this.showToast('Validation failed', 'error'); }).finally(() => { this.isValidating = false; }); }, getPreviewUrl() { return `report/${this.valAccessnumber}`; }, })); });