From 181938ea32cf2c11d10d56018df22586adff7860 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 11 Jan 2026 17:07:14 +0000
Subject: [PATCH] =CRUD.js cleanup, DataStore.js filtering optimization

---
 assets/js/concise/Queue.js |  155 ++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 102 insertions(+), 53 deletions(-)

diff --git a/assets/js/concise/Queue.js b/assets/js/concise/Queue.js
index 478be92..db25262 100644
--- a/assets/js/concise/Queue.js
+++ b/assets/js/concise/Queue.js
@@ -152,7 +152,7 @@
 		window.addEventListener('beforeunload', this.unloadHandler);
 	}
 		handleOnline() {
-			this.updatePanel();
+			this.updatePanel('synced');
 			if (this.getQueueByStatus(this.pendingStatuses).length > 0) {
 				this.processQueue();
 			}
@@ -174,6 +174,7 @@
 			const refresh = window.targetCheck(e, this.selectors.refresh.button);
 			if (refresh) {
 				this.store.clearCache();
+				this.store.clearFilters();
 				this.store.fetch();
 				return;
 			}
@@ -304,6 +305,7 @@
 			title: 'Operation',
 			status: 'queued',
 			timestamp: Date.now(),
+			created_at: new Date().toISOString(),
 			retries: 0,
 			user: this.user,
 			... operation
@@ -441,8 +443,7 @@
 		this.setProcessing(false);
 		this.stopActivityTracking();
 
-		// this.toggleQueue(this.maybeStartPolling());
-
+		this.toggleQueue(this.maybeStartPolling());
 	}
 
 	async processOperation(operation) {
@@ -545,13 +546,37 @@
 		});
 	}
 
+	sortOperations(ops) {
+		const statusPriority = {
+			'processing': 0,
+			'uploading': 1,
+			'pending': 2,
+			'queued': 3,
+			'localProcessing': 4,
+			'failed': 5,
+			'completed': 6,
+			'failed_permanent': 7
+		};
+
+		return ops.sort((a, b) => {
+			// First by status priority
+			const priorityDiff = (statusPriority[a.status] ?? 99) - (statusPriority[b.status] ?? 99);
+			if (priorityDiff !== 0) return priorityDiff;
+
+			// Then by updated_at (most recent first)
+			const aTime = a.updated_at ?? a.timestamp ?? 0;
+			const bTime = b.updated_at ?? b.timestamp ?? 0;
+			return new Date(bTime) - new Date(aTime);
+		});
+	}
+
 	getAllQueue() {
 		let ops = [... new Set([
 			...Array.from(this.store.data.values()),
 			... Array.from(this.queue.values())
 		])];
 		//Sort operations by operation updated_at
-		return this.sortByDate(ops);
+		return this.sortOperations(ops);
 	}
 
 	getQueueByStatus(status) {
@@ -563,7 +588,7 @@
 			...Array.from(this.store.filterByIndex({status: status})),
 			...Array.from(this.queue.values()).filter(op => status.includes(op.status))
 		])];
-		return this.sortByDate(ops);
+		return this.sortOperations(ops);
 	}
 
 
@@ -589,32 +614,53 @@
 	 POLLING
 	****************************************************************************/
 	maybeStartPolling() {
-		const incomplete = this.getQueueByStatus(this.pendingStatuses);
+		const incomplete = this.getQueueByStatus([...this.pendingStatuses, ...this.workingStatuses]);
 		if (incomplete.length > 0) {
 			this.startPolling();
 			return true;
 		}
+		this.updatePanel('synced');
 		return false;
 	}
 	startPolling() {
 		if (this.isPolling) return;
 		this.isPolling = true;
 		this.updatePanel('pending');
+		this.runPollCycle();
+	}
 
-		this.pollTimer = setInterval(async () => {
-			try {
-				this.store.clearCache();
-				await this.store.fetch();
-				if (!this.maybeStartPolling()) {
-					this.stopPolling();
-					this.updatePanel('synced');
-				}
-			} catch (error) {
-				console.error('Polling error:', error);
+	async runPollCycle() {
+		if (!this.isPolling) return;
+
+		try {
+			this.store.clearCache();
+			await this.store.fetch();
+			if (!this.maybeStartPolling()) {
+				this.stopPolling();
+				this.updatePanel('synced');
+				return;
 			}
-		},
-			5000);
-		this.startCountdown();
+		} catch (error) {
+			console.error('Polling error:', error);
+		}
+
+		// Schedule next poll with countdown
+		this.startCountdown(5, () => this.runPollCycle());
+	}
+
+	startCountdown(count, onComplete) {
+		this.ui.refresh.countdown.classList.add('counting');
+		this.ui.refresh.countdown.textContent = count;
+
+		this.countdownTimer = setInterval(() => {
+			count--;
+			if (count > 0) {
+				this.ui.refresh.countdown.textContent = count;
+			} else {
+				this.stopCountdown();
+				if (onComplete) onComplete();
+			}
+		}, 1000);
 	}
 
 	stopPolling() {
@@ -626,24 +672,14 @@
 		}
 		this.stopCountdown();
 	}
-	startCountdown(count = 5) {
-		if (!this.isPolling) return;
-		this.ui.refresh.countdown.textContent = count;
-		this.countdownTimer = setInterval(async() => {
-			count--;
-			if (count >= 0) {
-				this.ui.refresh.countdown.textContent = count;
-			}else {
-				this.ui.refresh.countdown.textContent = '';
-				this.stopCountdown();
-			}
-		},1000);
-	}
+
 	stopCountdown() {
 		if (this.countdownTimer) {
 			clearInterval(this.countdownTimer);
 			this.countdownTimer = null;
 		}
+		this.ui.refresh.countdown.classList.remove('counting');
+		this.ui.refresh.countdown.textContent = '';
 	}
 	/****************************************************************************
 	 UI
@@ -661,6 +697,12 @@
 			this.ui.actions.retry.disabled = operations.filter(op => op.status === 'failed').length === 0;
 			this.ui.actions.clear.disabled = operations.filter(op => op.status === 'completed').length ===0;
 
+			const activeCount = operations.filter(op =>
+				[...this.pendingStatuses, ...this.workingStatuses].includes(op.status)
+			).length;
+			this.ui.toggle.count.hidden = activeCount === 0;
+			this.ui.toggle.count.textContent = activeCount;
+
 			for (let status of this.statuses) {
 				if (status === 'failed_permanent') continue;
 				let total = operations.filter(op => op.status === status).length;
@@ -681,26 +723,38 @@
 
 		const status = this.store.filters?.status ?? 'all';
 		const operations = (status === 'all') ? this.getAllQueue() : this.getQueueByStatus(status);
+		const sortedOps = this.sortOperations(operations);
 
-		window.removeChildren(this.ui.items.container);
-
-		if (operations.length === 0) {
+		if (sortedOps.length === 0) {
+			window.removeChildren(this.ui.items.container);
 			const empty = window.getTemplate('emptyQueue');
 			this.ui.items.container.append(empty);
 			this.a11y.announce('No items in queue');
 			return;
+		} else {
+			this.ui.items.container.querySelector('.empty-group')?.remove();
 		}
 
-		operations.forEach(op => {
-			let item = this.items.get(op.id);
+		// Track which items should exist
+		const expectedIds = new Set(sortedOps.map(op => op.id));
 
+		// Remove items that shouldn't exist
+		this.items.forEach((item, id) => {
+			if (!expectedIds.has(id)) {
+				item.element?.remove();
+				this.items.delete(id);
+			}
+		});
+
+		// Update/add items in order
+		sortedOps.forEach((op, index) => {
+			let item = this.items.get(op.id);
 			if (!item) {
-				// Create new element and reference
 				item = this.createOperationElement(op);
 			}
-
 			if (item?.element) {
 				this.updateOperationUI(op.id);
+				// Reorder by re-appending (moves to end in correct order)
 				this.ui.items.container.append(item.element);
 			}
 		});
@@ -742,20 +796,15 @@
 				item.ui.startedAt.textContent = window.formatTimeAgo(op.created_at);
 			}
 			let text = op.status === 'completed' ? 'Completed: ' : 'Last updated: ';
-			let shouldShow =Object.hasOwn(op, 'updated_at') || Object.hasOwn(op, 'completed_at');
-			item.ui.completed.wrap.hidden = !shouldShow;
-			if (shouldShow && item.ui.completed.label && item.ui.completed.time) {
-				let time;
-				if (Object.hasOwn(op, 'completed_at')) {
-					time = op.completed_at;
-				} else {
-					time = op.updated_at;
-				}
-
-				item.ui.completed.label.textContent = text;
-				item.ui.completed.time.setAttribute('datetime', time);
-				item.ui.completed.time.textContent = window.formatTimeAgo(time);
+			const shouldShowCompleted = op.status === 'completed' && (op.completed_at || op.updated_at);
+			item.ui.completed.wrap.hidden = !shouldShowCompleted;
+			if (shouldShowCompleted) {
+				const completedTime = op.completed_at ?? op.updated_at;
+				item.ui.completed.label.textContent = 'Completed: ';
+				item.ui.completed.time.setAttribute('datetime', completedTime);
+				item.ui.completed.time.textContent = window.formatTimeAgo(completedTime);
 			}
+
 			window.showProgress(item.ui.progress, progress, 100, this.statusLabel(op.status));
 			if (item.ui.actions.cancel) item.ui.actions.cancel.hidden = this.completedStatuses.includes(op.status);
 			if (item.ui.actions['retry']) {
@@ -786,7 +835,7 @@
 
 	updatePanel(status = 'syncing') {
 		if (!this.panelStatuses.includes(status)) return;
-		this.ui.panel.classList.remove(this.panelStatuses);
+		this.ui.panel.classList.remove(...this.panelStatuses);
 		this.ui.panel.classList.add(status);
 	}
 	/****************************************************************************

--
Gitblit v1.10.0