fix: additional memory leak prevention in Alpine.js watchers and audit events

- Add toastTimeouts array to track and clear pending timeouts
- Consolidate duplicate watchers (sortCol, sortAsc, sorted, currentPage)
- Add destroy() method to clean up data structures and timeouts
- Cache audit events in _cachedAuditEvents instead of computed getter
- Clear item data when closing dialogs
This commit is contained in:
mahdahar 2026-02-16 07:17:09 +07:00
parent 3577ee870f
commit 79e6ab63a0

View File

@ -112,50 +112,68 @@ document.addEventListener('alpine:init', () => {
this.totalPages = Math.ceil(this.filtered.length / this.pageSize) || 1;
},
// Track timeouts for cleanup
toastTimeouts: [],
init() {
this.today = new Date().toISOString().slice(0, 10);
this.filter.date1 = this.today;
this.filter.date2 = this.today;
this.$watch('filterTable', () => {
this.currentPage = 1;
});
const defaultPrinter = '<?= $config[session()->get("userrole")]["sampleDialog"]["defaultPrinter"] ?? "lab" ?>';
this.selectedPrinter = defaultPrinter || 'lab';
// Set up watchers to update cached computed properties
// Single consolidated watcher for list changes
this.$watch('list', () => {
this.computeFiltered();
this.computeValidatedCount();
});
this.$watch('filterKey', () => {
this.computeFiltered();
});
// Single watcher for filter changes
this.$watch('filterKey', () => this.computeFiltered());
this.$watch('filterTable', () => {
this.currentPage = 1;
this.computeFiltered();
});
// Single watcher for computed chain
this.$watch('filtered', () => {
this.computeSorted();
this.computeTotalPages();
});
this.$watch('sortCol', () => {
this.computeSorted();
});
this.$watch('sortAsc', () => {
this.computeSorted();
});
this.$watch('sorted', () => {
this.computePaginated();
});
this.$watch('currentPage', () => {
this.computePaginated();
// Sort changes trigger pagination update
this.$watch('sortCol', () => this.computeSorted());
this.$watch('sortAsc', () => this.computeSorted());
this.$watch('sorted', () => this.computePaginated());
this.$watch('currentPage', () => this.computePaginated());
// Watch audit data changes to recompute events
this.$watch('auditData', () => {
this.computeAuditEvents();
});
// Register cleanup on destroy
this.$cleanup(() => this.destroy());
this.fetchList();
},
// Cleanup method to prevent memory leaks
destroy() {
// Clear all tracked timeouts
this.toastTimeouts.forEach(id => clearTimeout(id));
this.toastTimeouts = [];
// Clear large data structures
this.list = [];
this.filtered = [];
this.sorted = [];
this.paginated = [];
this.auditData = null;
this.item = null;
this.previewItem = null;
},
fetchList() {
this.isLoading = true;
@ -215,6 +233,7 @@ document.addEventListener('alpine:init', () => {
closeSampleDialog() {
this.isDialogSampleOpen = false;
this.item = null;
},
fetchItem(accessnumber) {
@ -282,9 +301,12 @@ document.addEventListener('alpine:init', () => {
auditData: null,
auditAccessnumber: null,
auditTab: 'all',
// Cached audit events to prevent memory leak
_cachedAuditEvents: [],
openAuditDialog(accessnumber) {
this.auditAccessnumber = accessnumber;
this.auditData = null;
this._cachedAuditEvents = [];
this.auditTab = 'all';
this.isDialogAuditOpen = true;
this.fetchAudit(accessnumber);
@ -292,6 +314,7 @@ document.addEventListener('alpine:init', () => {
closeAuditDialog() {
this.isDialogAuditOpen = false;
this.auditData = null;
this._cachedAuditEvents = [];
this.auditAccessnumber = null;
},
fetchAudit(accessnumber) {
@ -300,10 +323,14 @@ document.addEventListener('alpine:init', () => {
headers: { 'Content-Type': 'application/json' }
}).then(res => res.json()).then(data => {
this.auditData = data.data;
this.computeAuditEvents();
});
},
get getAllAuditEvents() {
if (!this.auditData) return [];
computeAuditEvents() {
if (!this.auditData) {
this._cachedAuditEvents = [];
return;
}
let events = [];
let id = 0;
@ -367,15 +394,18 @@ document.addEventListener('alpine:init', () => {
});
});
return events.sort((a, b) => {
this._cachedAuditEvents = events.sort((a, b) => {
if (!a.datetime) return 1;
if (!b.datetime) return -1;
return new Date(a.datetime) - new Date(b.datetime);
});
},
get getAllAuditEvents() {
return this._cachedAuditEvents;
},
get getFilteredAuditEvents() {
if (this.auditTab === 'all') return this.getAllAuditEvents;
return this.getAllAuditEvents.filter(e => e.category === this.auditTab);
if (this.auditTab === 'all') return this._cachedAuditEvents;
return this._cachedAuditEvents.filter(e => e.category === this.auditTab);
},
@ -410,9 +440,6 @@ document.addEventListener('alpine:init', () => {
}
},
selectedPrinter: 'lab',
printAllLabels(accessnumber) {
const printer = this.selectedPrinter || 'lab';
fetch(`${BASEURL}/label/all/${accessnumber}/${printer}`, { method: 'GET' })
@ -530,7 +557,13 @@ document.addEventListener('alpine:init', () => {
toast.className = `alert alert-${type} fixed top-4 right-4 z-50`;
toast.innerHTML = `<i class="fa ${type === 'error' ? 'fa-times-circle' : 'fa-check-circle'}"></i> ${message}`;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 2000);
const timeoutId = setTimeout(() => {
toast.remove();
// Remove from tracking array
const idx = this.toastTimeouts.indexOf(timeoutId);
if (idx > -1) this.toastTimeouts.splice(idx, 1);
}, 2000);
this.toastTimeouts.push(timeoutId);
},
}));
});