190 lines
8.2 KiB
TypeScript
190 lines
8.2 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { useOnlineStatus } from '@/hooks/useOnlineStatus';
|
|
import { offlineQueue } from '@/services/offlineQueue';
|
|
import { WifiOff, Loader2, List } from 'lucide-react';
|
|
import { hasRecentNetworkError } from '@/utils/networkErrorTracker';
|
|
import { OfflineQueueManager } from './OfflineQueueManager';
|
|
|
|
/**
|
|
* Composant pour afficher un indicateur de mode hors ligne avec nombre de requêtes en attente
|
|
*/
|
|
export function OfflineIndicator() {
|
|
const isOnline = useOnlineStatus();
|
|
const [queueSize, setQueueSize] = useState(0);
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
const [hasNetworkError, setHasNetworkError] = useState(false);
|
|
const [showQueueManager, setShowQueueManager] = useState(false);
|
|
const [shouldShowSyncBar, setShouldShowSyncBar] = useState(false);
|
|
|
|
// Mettre à jour la taille de la file d'attente
|
|
useEffect(() => {
|
|
const updateQueueSize = () => {
|
|
const size = offlineQueue.getQueueSize();
|
|
// #region agent log
|
|
// fetch('http://127.0.0.1:7242/ingest/09c5ea5e-2380-4cc3-92aa-d26f3b3d26f6',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OfflineIndicator.tsx:updateQueueSize',message:'Queue size updated',data:{queueSize:size,isOnline},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'C'})}).catch(()=>{});
|
|
// #endregion
|
|
setQueueSize(size);
|
|
};
|
|
|
|
// Mettre à jour immédiatement
|
|
updateQueueSize();
|
|
|
|
// Mettre à jour toutes les secondes
|
|
const interval = setInterval(updateQueueSize, 1000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
// Vérifier si la file est en cours de traitement
|
|
useEffect(() => {
|
|
if (isOnline && queueSize > 0) {
|
|
setIsProcessing(true);
|
|
// Vérifier périodiquement si le traitement est terminé
|
|
const checkProcessing = setInterval(() => {
|
|
const currentSize = offlineQueue.getQueueSize();
|
|
if (currentSize === 0) {
|
|
setIsProcessing(false);
|
|
clearInterval(checkProcessing);
|
|
}
|
|
}, 500);
|
|
return () => clearInterval(checkProcessing);
|
|
} else {
|
|
setIsProcessing(false);
|
|
return undefined;
|
|
}
|
|
}, [isOnline, queueSize]);
|
|
|
|
// Check for recent network errors
|
|
useEffect(() => {
|
|
const checkNetworkError = () => {
|
|
setHasNetworkError(hasRecentNetworkError());
|
|
};
|
|
|
|
// Check immediately
|
|
checkNetworkError();
|
|
|
|
// Check periodically (every 2 seconds) to update when error expires
|
|
const interval = setInterval(checkNetworkError, 2000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
// FIX: Délai avant d'afficher la barre de synchronisation pour éviter les flashs
|
|
useEffect(() => {
|
|
// #region agent log
|
|
// fetch('http://127.0.0.1:7242/ingest/09c5ea5e-2380-4cc3-92aa-d26f3b3d26f6',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OfflineIndicator.tsx:useEffect',message:'Sync bar effect triggered',data:{isProcessing,queueSize,isOnline,shouldShowSyncBar},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{});
|
|
// #endregion
|
|
if (isProcessing && queueSize > 0 && isOnline) {
|
|
const timer = setTimeout(() => {
|
|
// #region agent log
|
|
// fetch('http://127.0.0.1:7242/ingest/09c5ea5e-2380-4cc3-92aa-d26f3b3d26f6',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OfflineIndicator.tsx:setTimeout',message:'Setting shouldShowSyncBar to true',data:{queueSize,isProcessing},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{});
|
|
// #endregion
|
|
setShouldShowSyncBar(true);
|
|
}, 500);
|
|
return () => {
|
|
clearTimeout(timer);
|
|
setShouldShowSyncBar(false);
|
|
};
|
|
} else {
|
|
setShouldShowSyncBar(false);
|
|
return undefined;
|
|
}
|
|
}, [isProcessing, queueSize, isOnline]);
|
|
|
|
// Ne rien afficher si en ligne, aucune requête en attente, et pas d'erreur réseau récente
|
|
if (isOnline && queueSize === 0 && !isProcessing && !hasNetworkError) {
|
|
return null;
|
|
}
|
|
|
|
// Mode hors ligne ou erreur réseau récente
|
|
if (!isOnline || hasNetworkError) {
|
|
return (
|
|
<>
|
|
<div className="fixed top-0 left-0 right-0 bg-kodo-red/90 backdrop-blur-sm text-white px-4 py-2.5 text-sm z-50 flex items-center justify-center gap-2 shadow-lg border-b border-kodo-red">
|
|
<WifiOff className="w-4 h-4" />
|
|
<span>
|
|
Mode hors ligne
|
|
{queueSize > 0 && (
|
|
<span className="ml-2 font-semibold">
|
|
- {queueSize} {queueSize === 1 ? 'requête' : 'requêtes'} en
|
|
attente
|
|
</span>
|
|
)}
|
|
</span>
|
|
{queueSize > 0 && (
|
|
<button
|
|
onClick={() => setShowQueueManager(true)}
|
|
className="ml-3 px-2 py-1 bg-white/10 hover:bg-white/20 rounded border border-white/20 transition-colors flex items-center gap-1.5 text-xs font-medium"
|
|
title="View queued requests"
|
|
>
|
|
<List className="w-3.5 h-3.5" />
|
|
View Queue
|
|
</button>
|
|
)}
|
|
</div>
|
|
<OfflineQueueManager
|
|
open={showQueueManager}
|
|
onClose={() => setShowQueueManager(false)}
|
|
/>
|
|
</>
|
|
);
|
|
}
|
|
|
|
// En ligne mais traitement de la file en cours
|
|
// FIX: Ne pas afficher la barre si les requêtes sont rapidement traitées (moins de 500ms)
|
|
// Cela évite d'afficher la barre pour des requêtes qui sont déjà en cours de traitement
|
|
// #region agent log
|
|
if (isProcessing && queueSize > 0) {
|
|
// fetch('http://127.0.0.1:7242/ingest/09c5ea5e-2380-4cc3-92aa-d26f3b3d26f6',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OfflineIndicator.tsx:123',message:'Checking sync bar display',data:{isProcessing,queueSize,shouldShowSyncBar,willShow:shouldShowSyncBar},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{});
|
|
}
|
|
// #endregion
|
|
if (isProcessing && queueSize > 0 && shouldShowSyncBar) {
|
|
return (
|
|
<>
|
|
<div className="fixed top-0 left-0 right-0 bg-kodo-cyan/90 backdrop-blur-sm text-kodo-void px-4 py-2.5 text-sm z-50 flex items-center justify-center gap-2 shadow-lg border-b border-kodo-steel">
|
|
<Loader2 className="w-4 h-4 animate-spin" />
|
|
<span>
|
|
Synchronisation en cours
|
|
{queueSize > 0 && (
|
|
<span className="ml-2 font-semibold">
|
|
- {queueSize} {queueSize === 1 ? 'requête' : 'requêtes'} restante
|
|
{queueSize > 1 ? 's' : ''}
|
|
</span>
|
|
)}
|
|
</span>
|
|
{queueSize > 0 && (
|
|
<>
|
|
<button
|
|
onClick={async () => {
|
|
// #region agent log
|
|
// fetch('http://127.0.0.1:7242/ingest/09c5ea5e-2380-4cc3-92aa-d26f3b3d26f6',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OfflineIndicator.tsx:clearQueue',message:'User clicked clear queue',data:{queueSize},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'C'})}).catch(()=>{});
|
|
// #endregion
|
|
await offlineQueue.clearQueue();
|
|
setQueueSize(0);
|
|
}}
|
|
className="ml-2 px-2 py-1 bg-kodo-red/20 hover:bg-kodo-red/30 rounded border border-kodo-red/30 transition-colors flex items-center gap-1.5 text-xs font-medium"
|
|
title="Clear queued requests"
|
|
>
|
|
Clear Queue
|
|
</button>
|
|
<button
|
|
onClick={() => setShowQueueManager(true)}
|
|
className="ml-2 px-2 py-1 bg-kodo-void/20 hover:bg-kodo-void/30 rounded border border-kodo-void/30 transition-colors flex items-center gap-1.5 text-xs font-medium"
|
|
title="View queued requests"
|
|
>
|
|
<List className="w-3.5 h-3.5" />
|
|
View Queue
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
<OfflineQueueManager
|
|
open={showQueueManager}
|
|
onClose={() => setShowQueueManager(false)}
|
|
/>
|
|
</>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|