document.addEventListener('alpine:init', () => { Alpine.data("dashboard", () => ({ // dashboard today: "", filter: { date1: "", date2: "" }, list: [], isLoading: false, counters: { Pend: 0, Coll: 0, Recv: 0, Inc: 0, Fin: 0, Total: 0 }, statusColor: { Pend: 'bg-white text-black font-bold', PartColl: 'bg-orange-300 text-white font-bold', Coll: 'bg-orange-500 text-white font-bold', PartRecv: 'bg-blue-200 text-black font-bold', Recv: 'bg-blue-500 text-white font-bold', Inc: 'bg-yellow-500 text-white font-bold', Fin: 'bg-green-500 text-white font-bold', }, filterTable: "", filterKey: 'Total', statusMap: { Total: [], Pend: ['Pend'], Coll: ['Coll', 'PartColl'], Recv: ['Recv', 'PartRecv'], Inc: ['Inc'], Fin: ['Fin'], }, // Sorting & Pagination sortCol: 'REQDATE', sortAsc: false, currentPage: 1, pageSize: 30, sort(col) { if (this.sortCol === col) { this.sortAsc = !this.sortAsc; } else { this.sortCol = col; this.sortAsc = true; } }, nextPage() { if (this.currentPage < this.totalPages) this.currentPage++; }, prevPage() { if (this.currentPage > 1) this.currentPage--; }, get totalPages() { return Math.ceil(this.filtered.length / this.pageSize) || 1; }, get sorted() { return this.filtered.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; }); }, get paginated() { const start = (this.currentPage - 1) * this.pageSize; const end = start + this.pageSize; return this.sorted.slice(start, end); }, init() { this.today = new Date().toISOString().slice(0, 10); // Check if running on development workstation (localhost) const isDev = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' || window.location.hostname.includes('.test'); if (isDev) { // Development: specific date range for test data this.filter.date1 = '2025-01-02'; this.filter.date2 = '2025-01-03'; } else { // Production: default to today this.filter.date1 = this.today; this.filter.date2 = this.today; } this.$watch('filterTable', () => { this.currentPage = 1; }); this.fetchList(); }, fetchList() { this.isLoading = true; this.list = []; let statusOrder = { Pend: 1, PartColl: 2, Coll: 3, PartRecv: 4, Recv: 5, Inc: 6, Fin: 7 }; let param = new URLSearchParams(this.filter).toString(); for (let k in this.counters) { this.counters[k] = 0; } fetch(`${BASEURL}/api/requests?${param}`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }).then(res => res.json()).then(data => { this.list = data.data ?? []; this.filterKey = 'Total'; this.list.forEach(item => { if (this.counters[item.STATS] !== undefined) { this.counters[item.STATS]++; this.counters.Total++; } else { if (item.STATS == 'PartColl') { this.counters.Coll++; } else if (item.STATS == 'PartRecv') { this.counters.Recv++; } this.counters.Total++; } }); this.list.sort((a, b) => { let codeA = statusOrder[a.STATS] ?? 0; let codeB = statusOrder[b.STATS] ?? 0; return codeA - codeB; }); }).finally(() => { this.isLoading = false; }); }, reset() { this.filter.date1 = this.today; this.filter.date2 = this.today; this.fetchList(); }, isValidated(item) { return item.ISVAL == 1 && item.ISPENDING != 1; }, get filtered() { let filteredList = this.list; if (this.filterKey === 'Validated') { filteredList = filteredList.filter(item => this.isValidated(item)); } else { const validStatuses = this.statusMap[this.filterKey]; if (validStatuses.length > 0) { filteredList = filteredList.filter(item => validStatuses.includes(item.STATS)); } } if (this.filterTable) { const searchTerm = this.filterTable.toLowerCase(); filteredList = filteredList.filter(item => Object.values(item).some(value => String(value).toLowerCase().includes(searchTerm) ) ); } return filteredList; }, get validatedCount() { return this.list.filter(r => this.isValidated(r)).length; }, /* sample dialog */ item: '', isDialogSampleOpen: false, isSampleLoading: false, openSampleDialog(accessnumber) { this.isDialogSampleOpen = true; this.fetchItem(accessnumber) }, closeSampleDialog() { this.isDialogSampleOpen = false; }, fetchItem(accessnumber) { this.isSampleLoading = true; this.item = []; fetch(`${BASEURL}/api/samples/${accessnumber}`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }) .then(res => res.json()).then(data => { this.item = data.data ?? {}; if (!Array.isArray(this.item.samples)) this.item.samples = []; }).finally(() => { this.isSampleLoading = false; }); }, collect(sampcode, accessnumber) { fetch(`${BASEURL}/api/samples/collect/${accessnumber}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ samplenumber: sampcode, userid: '' }) }) .then(res => res.json()).then(data => { this.fetchItem(accessnumber); }); }, uncollect(sampcode, accessnumber) { if (!confirm(`Uncollect sample ${sampcode} from request ${accessnumber}?`)) { return; } fetch(`${BASEURL}/api/samples/collect/${accessnumber}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ samplenumber: sampcode, userid: '' }) }) .then(res => res.json()).then(data => { this.fetchItem(accessnumber); }); }, unreceive(sampcode, accessnumber) { if (!confirm(`Unreceive sample ${sampcode} from request ${accessnumber}?`)) { return; } fetch(`${BASEURL}/api/samples/unreceive/${accessnumber}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ samplenumber: sampcode, userid: '' }) }) .then(res => res.json()).then(data => { this.fetchItem(accessnumber); }); }, /* preview dialog */ isDialogPreviewOpen: false, reviewed: false, previewItem: null, previewAccessnumber: null, previewType: 'preview', openPreviewDialog(accessnumber, type, item) { this.previewAccessnumber = accessnumber; this.previewItem = item; this.previewType = type; this.isDialogPreviewOpen = true; this.reviewed = false; }, closePreviewDialog() { this.isDialogPreviewOpen = false; this.previewItem = null; }, setPreviewType(type) { this.previewType = type; }, getPreviewUrl() { let base = 'http://glenlis/spooler_db/main_dev.php'; let url = `${base}?acc=${this.previewAccessnumber}`; if (this.previewType === 'ind') url += '&lang=ID'; if (this.previewType === 'eng') url += '&lang=EN'; if (this.previewType === 'pdf') url += '&output=pdf'; return url; }, validate(accessnumber, userid) { fetch(`${BASEURL}/api/requests/validate/${accessnumber}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userid: `${userid}` }) }).then(response => { this.closePreviewDialog(); this.fetchList(); console.log('Validate clicked for', this.previewAccessnumber, 'by user', userid); }); }, /* unvalidate dialog */ isDialogUnvalOpen: false, unvalReason: '', unvalAccessnumber: null, openUnvalDialog(accessnumber) { this.unvalReason = ''; this.isDialogUnvalOpen = true; this.unvalAccessnumber = accessnumber; }, unvalidate(accessnumber, userid) { if (!confirm(`Unvalidate request ${accessnumber}?`)) { return; } fetch(`${BASEURL}/api/requests/validate/${accessnumber}`, { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userid: `${userid}`, comment: this.unvalReason.trim() }) }).then(response => { this.closeUnvalDialog(); this.fetchList(); console.log(`Unvalidate clicked for ${accessnumber}, by user ${userid}`); }); }, closeUnvalDialog() { this.isDialogUnvalOpen = false; }, })); });