gdc_cmod/app/Views/shared/script_validation.php
mahdahar 4a14dbddce Fix validate page PDF queueing
Use the local accessnumber when queuing PDFs after val2, since the validation response does not include accessnumber. Add success and error toast feedback for the PDF generation request.
2026-04-21 12:26:56 +07:00

301 lines
8.5 KiB
PHP

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
);
const filteredLength = this.unvalidatedFiltered.length;
if (filteredLength > 0) {
const nextIndex = Math.min(this.currentIndex, filteredLength - 1);
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 `<?=base_url()?>report/${this.valAccessnumber}`;
},
}));
});