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è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, ); }