From 05fc3b004828631066fddf48577a2b08784fcf78 Mon Sep 17 00:00:00 2001
From: stephan
Date: Wed, 15 Jul 2026 09:18:35 -0400
Subject: [PATCH] =?UTF-8?q?MSIN-4328=20=E2=80=94=20Implement=20countdown?=
=?UTF-8?q?=20for=20next=20auto=20sync=20in=20ChronoTrack=20API.=20Enhance?=
=?UTF-8?q?=20UI=20to=20display=20countdown=20timer=20for=20upcoming=20cro?=
=?UTF-8?q?n=20jobs=20and=20update=20push=20panel=20messages=20for=20clari?=
=?UTF-8?q?ty=20on=20sync=20operations.=20Introduce=20new=20functions=20fo?=
=?UTF-8?q?r=20managing=20auto=20sync=20metadata=20and=20improve=20overall?=
=?UTF-8?q?=20user=20feedback=20during=20synchronization=20processes.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
php/chronotrack_api/fx_chronotrack_admin.php | 70 +++++++++++++++++++-
php/chronotrack_api/fx_chronotrack_sync.php | 39 +++++++++++
2 files changed, 107 insertions(+), 2 deletions(-)
diff --git a/php/chronotrack_api/fx_chronotrack_admin.php b/php/chronotrack_api/fx_chronotrack_admin.php
index 59da8fd..3334f24 100644
--- a/php/chronotrack_api/fx_chronotrack_admin.php
+++ b/php/chronotrack_api/fx_chronotrack_admin.php
@@ -335,7 +335,8 @@ function fxChronotrackApiAdminRenderPushPanel($intEveId, $blnClosed = false, $ar
. 'Lancer le script cron';
echo '';
echo 'Envoi manuel : nouveaux / modifiés depuis le dernier push. '
- . 'Page : refresh statut MS1 chaque minute.
';
+ . 'Refresh écran (~60 s) = statut MS1 seulement, aucun envoi vers ChronoTrack. '
+ . 'L’envoi auto = cron auto_chronotrack_sync.php.
';
echo '';
echo '';
@@ -453,11 +454,71 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
var intCtLiveCount = null;
var blnPushRunning = false;
var intStatusRefreshTimer = null;
+ var intAutoCountdownTimer = null;
+ // MSIN-4328 — countdown prochain passage cron auto (pas le refresh écran)
+ var objAutoCd = {
+ enabled: false,
+ nextDueTs: 0,
+ serverNowTs: 0,
+ receivedAtMs: 0
+ };
function escHtml(str) {
return String(str).replace(/&/g, '&').replace(//g, '>');
}
+ function formatCountdownMmSs(intSec) {
+ intSec = Math.max(0, parseInt(intSec, 10) || 0);
+ var intM = Math.floor(intSec / 60);
+ var intS = intSec % 60;
+ return intM + ':' + (intS < 10 ? '0' : '') + intS;
+ }
+
+ function paintAutoCountdown() {
+ var $status = $('#ct_api_push_status');
+ if (!$status.length) {
+ return;
+ }
+ var $el = $('#ct_api_next_auto');
+ if (!objAutoCd.enabled) {
+ $el.remove();
+ return;
+ }
+ if (!$el.length) {
+ $status.append('');
+ $el = $('#ct_api_next_auto');
+ }
+ var intElapsed = Math.floor((Date.now() - objAutoCd.receivedAtMs) / 1000);
+ var intServerNow = objAutoCd.serverNowTs + intElapsed;
+ var intLeft = objAutoCd.nextDueTs - intServerNow;
+ if (intLeft <= 0) {
+ $el.html('Prochain envoi auto : dû (dès le prochain cron, ~chaque minute)');
+ } else {
+ $el.html('Prochain envoi auto dans ' + formatCountdownMmSs(intLeft) + '');
+ }
+ }
+
+ function syncAutoCountdownFromPreview(res) {
+ var a = (res && res.auto_sync) ? res.auto_sync : null;
+ if (!a || !a.enabled || !a.next_due_ts) {
+ objAutoCd.enabled = false;
+ if (intAutoCountdownTimer) {
+ clearInterval(intAutoCountdownTimer);
+ intAutoCountdownTimer = null;
+ }
+ $('#ct_api_next_auto').remove();
+ return;
+ }
+ objAutoCd.enabled = true;
+ objAutoCd.nextDueTs = parseInt(a.next_due_ts, 10) || 0;
+ objAutoCd.serverNowTs = parseInt(a.server_now_ts, 10) || Math.floor(Date.now() / 1000);
+ objAutoCd.receivedAtMs = Date.now();
+ paintAutoCountdown();
+ if (!intAutoCountdownTimer) {
+ intAutoCountdownTimer = setInterval(paintAutoCountdown, 1000);
+ }
+ }
+
if (window.Ms1Loader) {
Ms1Loader.init({
src:
@@ -945,6 +1006,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
}
$('#ct_api_push_blocked_wrap').addClass('d-none').empty();
$btn.prop('disabled', true);
+ syncAutoCountdownFromPreview(null);
return;
}
@@ -982,6 +1044,8 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
+ intEligible + ' éligibles · rien à envoyer pour le moment.'
);
}
+ // MSIN-4328 — mini countdown prochain envoi cron (écran = lecture seule)
+ syncAutoCountdownFromPreview(res);
}
// Détails (sous les boutons, zone repliable)
@@ -1194,11 +1258,13 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
$msg.addClass('text-success').text(res.message || 'Enregistré');
if (res.auto_sync) {
if (res.auto_sync.enabled && res.auto_sync.until) {
- $hint.text('Config OK — cron toutes les minutes lit cette config (auto_chronotrack_sync.php). Arrêt : '
+ $hint.text('Config OK — le cron envoie les changements (pas le refresh écran). Arrêt : '
+ res.auto_sync.until + '.');
} else {
$hint.text('Sync auto désactivée.');
+ $('#ct_api_next_auto').remove();
}
+ loadPushPreview(true);
}
}, 'json').fail(function () {
$msg.addClass('text-danger').text('Erreur réseau');
diff --git a/php/chronotrack_api/fx_chronotrack_sync.php b/php/chronotrack_api/fx_chronotrack_sync.php
index 70d42b5..428d688 100644
--- a/php/chronotrack_api/fx_chronotrack_sync.php
+++ b/php/chronotrack_api/fx_chronotrack_sync.php
@@ -1147,6 +1147,45 @@ function fxChronotrackApiSyncBuildPreview($intEveId) {
: null,
'last_push_ok_count' => intval($arrConfig['last_push_ok_count'] ?? 0),
'is_first_push' => ($strLastPushAt === '' || $strLastPushAt === '0000-00-00 00:00:00'),
+ // MSIN-4328 — infos countdown UI (le preview lui-même n’envoie rien à CT)
+ 'auto_sync' => fxChronotrackApiSyncPreviewAutoSyncMeta($arrConfig),
+ );
+}
+
+/**
+ * MSIN-4328 — méta sync auto pour countdown « prochain passage » (lecture seule).
+ */
+function fxChronotrackApiSyncPreviewAutoSyncMeta(array $arrConfig) {
+ $blnOn = (intval($arrConfig['auto_sync_enabled'] ?? 0) === 1);
+ $intInterval = intval($arrConfig['auto_sync_interval_min'] ?? 15);
+ if (!in_array($intInterval, fxChronotrackApiConfigAutoSyncIntervals(), true)) {
+ $intInterval = 15;
+ }
+ $strUntil = trim((string)($arrConfig['auto_sync_until'] ?? ''));
+ $strLast = trim((string)($arrConfig['last_auto_run_at'] ?? ''));
+ $intNow = time();
+ $intNextDue = null;
+ if ($blnOn) {
+ if ($strLast !== '' && $strLast !== '0000-00-00 00:00:00') {
+ $intLast = strtotime($strLast);
+ if ($intLast !== false) {
+ $intNextDue = $intLast + ($intInterval * 60);
+ } else {
+ $intNextDue = $intNow;
+ }
+ } else {
+ // Jamais passé = dû dès le prochain tick cron
+ $intNextDue = $intNow;
+ }
+ }
+
+ return array(
+ 'enabled' => $blnOn,
+ 'interval_min' => $intInterval,
+ 'until' => ($strUntil !== '' && $strUntil !== '0000-00-00 00:00:00') ? $strUntil : null,
+ 'last_auto_run_at' => ($strLast !== '' && $strLast !== '0000-00-00 00:00:00') ? $strLast : null,
+ 'server_now_ts' => $intNow,
+ 'next_due_ts' => $intNextDue,
);
}