Browse Source

Fix countdown NaN and negative values when decision date has passed

Two causes: (1) data-target-date attribute value had leading whitespace from PHP
template line break, causing new Date() to return Invalid Date → NaN; (2) no clamp
when date already passed → negative values displayed.

Fix: trim() the dataset value before parsing; short-circuit with all-zeros display
when diff is NaN or <= 0; remove the now <= target guard (replaced by early return).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Benjamin Harris 2 tuần trước cách đây
mục cha
commit
673d283e99
1 tập tin đã thay đổi với 32 bổ sung30 xóa
  1. 32 30
      contracts/progress.php

+ 32 - 30
contracts/progress.php

@@ -754,7 +754,7 @@ $fmtDate = function ($v) {
 			countdownEls.forEach(countdownEl => createCountdown(countdownEl))
 
 			function createCountdown(countdownEl) {
-				const target = new Date(countdownEl.dataset.targetDate);
+				const target = new Date((countdownEl.dataset.targetDate || '').trim());
 				const parts = {
 					days: {
 						text: ["days", "day"],
@@ -799,40 +799,42 @@ $fmtDate = function ($v) {
 			}
 
 			function getRemainingTime(target, parts, first = true) {
-				const now = new Date()
-				if (first) console.log({
-					target,
-					now
-				})
-				const remaining = {}
-				let seconds = Math.floor((target - (now)) / 1000);
+				const now = new Date();
+				const diff = target - now;
+
+				// Past or invalid date — freeze at all zeros
+				if (isNaN(diff) || diff <= 0) {
+					Object.entries(parts).forEach(([key, value]) => {
+						value.element.querySelector(".number").innerText = 0;
+						value.element.querySelector(".text").innerText = value.text[0];
+						value.element.querySelectorAll(".dot").forEach(dot => {
+							dot.dataset.active = false;
+							dot.dataset.lastactive = false;
+						});
+					});
+					return;
+				}
+
+				let seconds = Math.floor(diff / 1000);
 				let minutes = Math.floor(seconds / 60);
-				let hours = Math.floor(minutes / 60);
-				let days = Math.floor(hours / 24);
-				hours = hours - (days * 24);
-				minutes = minutes - (days * 24 * 60) - (hours * 60);
+				let hours   = Math.floor(minutes / 60);
+				let days    = Math.floor(hours / 24);
+				hours   = hours   - (days * 24);
+				minutes = minutes - (days * 24 * 60)  - (hours * 60);
 				seconds = seconds - (days * 24 * 60 * 60) - (hours * 60 * 60) - (minutes * 60);
-				Object.entries({
-					days,
-					hours,
-					minutes,
-					seconds
-				}).forEach(([key, value]) => {
+
+				Object.entries({ days, hours, minutes, seconds }).forEach(([key, value]) => {
 					const remaining = parts[key].element.querySelector(".number");
-					const text = parts[key].element.querySelector(".text");
+					const text      = parts[key].element.querySelector(".text");
 					remaining.innerText = value;
-					text.innerText = parts[key].text[Number(value == 1)]
-					const dots = parts[key].element.querySelectorAll(".dot")
-					dots.forEach((dot, idx) => {
-						dot.dataset.active = idx <= value;
-						dot.dataset.lastactive = idx == value;
-					})
-				})
-				if (now <= target) {
-					window.requestAnimationFrame(() => {
-						getRemainingTime(target, parts, false)
+					text.innerText      = parts[key].text[Number(value === 1)];
+					parts[key].element.querySelectorAll(".dot").forEach((dot, idx) => {
+						dot.dataset.active     = idx <= value;
+						dot.dataset.lastactive = idx === value;
 					});
-				}
+				});
+
+				window.requestAnimationFrame(() => getRemainingTime(target, parts, false));
 			}
 			document.getElementById('tryParse')?.addEventListener('click', function(e) {
 				e.preventDefault();