2026-01-11 15:30:43 +00:00
import { useEffect , useState } from 'react' ;
2025-12-03 21:56:50 +00:00
import { useOnlineStatus } from '@/hooks/useOnlineStatus' ;
2026-01-11 15:30:43 +00:00
import { offlineQueue } from '@/services/offlineQueue' ;
2026-01-15 17:02:40 +00:00
import { WifiOff , Loader2 , List } from 'lucide-react' ;
2026-01-11 16:16:49 +00:00
import { hasRecentNetworkError } from '@/utils/networkErrorTracker' ;
2026-01-15 17:02:40 +00:00
import { OfflineQueueManager } from './OfflineQueueManager' ;
2025-12-03 21:56:50 +00:00
/ * *
2026-01-11 15:30:43 +00:00
* Composant pour afficher un indicateur de mode hors ligne avec nombre de requêtes en attente
2025-12-03 21:56:50 +00:00
* /
export function OfflineIndicator() {
const isOnline = useOnlineStatus ( ) ;
2026-01-11 15:30:43 +00:00
const [ queueSize , setQueueSize ] = useState ( 0 ) ;
const [ isProcessing , setIsProcessing ] = useState ( false ) ;
2026-01-11 16:16:49 +00:00
const [ hasNetworkError , setHasNetworkError ] = useState ( false ) ;
2026-01-15 17:02:40 +00:00
const [ showQueueManager , setShowQueueManager ] = useState ( false ) ;
2026-01-18 12:55:28 +00:00
const [ shouldShowSyncBar , setShouldShowSyncBar ] = useState ( false ) ;
2025-12-03 21:56:50 +00:00
2026-01-11 15:30:43 +00:00
// Mettre à jour la taille de la file d'attente
useEffect ( ( ) = > {
const updateQueueSize = ( ) = > {
2026-01-18 12:55:28 +00:00
const size = offlineQueue . getQueueSize ( ) ;
// #region agent log
2026-01-18 15:28:22 +00:00
// 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(()=>{});
2026-01-18 12:55:28 +00:00
// #endregion
setQueueSize ( size ) ;
2026-01-11 15:30:43 +00:00
} ;
// 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 ) ;
2026-01-11 16:16:49 +00:00
return undefined ;
2026-01-11 15:30:43 +00:00
}
} , [ isOnline , queueSize ] ) ;
2026-01-11 16:16:49 +00:00
// 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 ) ;
} , [ ] ) ;
2026-01-18 12:55:28 +00:00
// FIX: Délai avant d'afficher la barre de synchronisation pour éviter les flashs
useEffect ( ( ) = > {
// #region agent log
2026-01-18 15:28:22 +00:00
// 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(()=>{});
2026-01-18 12:55:28 +00:00
// #endregion
if ( isProcessing && queueSize > 0 && isOnline ) {
const timer = setTimeout ( ( ) = > {
// #region agent log
2026-01-18 15:28:22 +00:00
// 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(()=>{});
2026-01-18 12:55:28 +00:00
// #endregion
setShouldShowSyncBar ( true ) ;
} , 500 ) ;
return ( ) = > {
clearTimeout ( timer ) ;
setShouldShowSyncBar ( false ) ;
} ;
} else {
setShouldShowSyncBar ( false ) ;
return undefined ;
}
} , [ isProcessing , queueSize , isOnline ] ) ;
2026-01-11 16:16:49 +00:00
// 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 ) {
2026-01-11 15:30:43 +00:00
return null ;
}
2026-01-11 16:16:49 +00:00
// Mode hors ligne ou erreur réseau récente
if ( ! isOnline || hasNetworkError ) {
2026-01-11 15:30:43 +00:00
return (
2026-01-15 17:02:40 +00:00
< >
refactor: Phase 3a — Global color class migration to SUMI semantics
- Replace all kodo-* color classes across ~100 TSX files:
kodo-void → background, kodo-ink → card, kodo-graphite → muted,
kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive,
kodo-lime → success, kodo-red → destructive, kodo-gold → warning
- Replace cyan-500, magenta-500, lime-500 default Tailwind colors with
semantic equivalents (primary, destructive, success)
- Fix WaveformVisualizer hardcoded hex colors to SUMI values
- Delete global-effects.css (conflicting, redundant with index.css)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 00:51:49 +00:00
< div className = "fixed top-0 left-0 right-0 bg-destructive/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-destructive" >
2026-01-15 17:02:40 +00:00
< 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 >
2026-01-11 15:30:43 +00:00
{ queueSize > 0 && (
2026-01-15 17:02:40 +00:00
< 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 >
2026-01-11 15:30:43 +00:00
) }
2026-01-15 17:02:40 +00:00
< / div >
< OfflineQueueManager
open = { showQueueManager }
onClose = { ( ) = > setShowQueueManager ( false ) }
/ >
< / >
2026-01-11 15:30:43 +00:00
) ;
}
// En ligne mais traitement de la file en cours
2026-01-18 12:55:28 +00:00
// 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
2026-01-11 15:30:43 +00:00
if ( isProcessing && queueSize > 0 ) {
2026-01-18 15:28:22 +00:00
// 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(()=>{});
2026-01-18 12:55:28 +00:00
}
// #endregion
if ( isProcessing && queueSize > 0 && shouldShowSyncBar ) {
2026-01-11 15:30:43 +00:00
return (
2026-01-15 17:02:40 +00:00
< >
refactor: Phase 3a — Global color class migration to SUMI semantics
- Replace all kodo-* color classes across ~100 TSX files:
kodo-void → background, kodo-ink → card, kodo-graphite → muted,
kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive,
kodo-lime → success, kodo-red → destructive, kodo-gold → warning
- Replace cyan-500, magenta-500, lime-500 default Tailwind colors with
semantic equivalents (primary, destructive, success)
- Fix WaveformVisualizer hardcoded hex colors to SUMI values
- Delete global-effects.css (conflicting, redundant with index.css)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 00:51:49 +00:00
< div className = "fixed top-0 left-0 right-0 bg-primary/90 backdrop-blur-sm text-foreground px-4 py-2.5 text-sm z-50 flex items-center justify-center gap-2 shadow-lg border-b border-border" >
2026-01-15 17:02:40 +00:00
< 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 >
2026-01-11 15:30:43 +00:00
{ queueSize > 0 && (
2026-01-18 12:55:28 +00:00
< >
< button
onClick = { async ( ) = > {
// #region agent log
2026-01-18 15:28:22 +00:00
// 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(()=>{});
2026-01-18 12:55:28 +00:00
// #endregion
await offlineQueue . clearQueue ( ) ;
setQueueSize ( 0 ) ;
} }
refactor: Phase 3a — Global color class migration to SUMI semantics
- Replace all kodo-* color classes across ~100 TSX files:
kodo-void → background, kodo-ink → card, kodo-graphite → muted,
kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive,
kodo-lime → success, kodo-red → destructive, kodo-gold → warning
- Replace cyan-500, magenta-500, lime-500 default Tailwind colors with
semantic equivalents (primary, destructive, success)
- Fix WaveformVisualizer hardcoded hex colors to SUMI values
- Delete global-effects.css (conflicting, redundant with index.css)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 00:51:49 +00:00
className = "ml-2 px-2 py-1 bg-destructive/20 hover:bg-destructive/30 rounded border border-destructive/30 transition-colors flex items-center gap-1.5 text-xs font-medium"
2026-01-18 12:55:28 +00:00
title = "Clear queued requests"
>
Clear Queue
< / button >
< button
onClick = { ( ) = > setShowQueueManager ( true ) }
refactor: Phase 3a — Global color class migration to SUMI semantics
- Replace all kodo-* color classes across ~100 TSX files:
kodo-void → background, kodo-ink → card, kodo-graphite → muted,
kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive,
kodo-lime → success, kodo-red → destructive, kodo-gold → warning
- Replace cyan-500, magenta-500, lime-500 default Tailwind colors with
semantic equivalents (primary, destructive, success)
- Fix WaveformVisualizer hardcoded hex colors to SUMI values
- Delete global-effects.css (conflicting, redundant with index.css)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 00:51:49 +00:00
className = "ml-2 px-2 py-1 bg-background/20 hover:bg-background/30 rounded border border-border/30 transition-colors flex items-center gap-1.5 text-xs font-medium"
2026-01-18 12:55:28 +00:00
title = "View queued requests"
>
< List className = "w-3.5 h-3.5" / >
View Queue
< / button >
< / >
2026-01-11 15:30:43 +00:00
) }
2026-01-15 17:02:40 +00:00
< / div >
< OfflineQueueManager
open = { showQueueManager }
onClose = { ( ) = > setShowQueueManager ( false ) }
/ >
< / >
2026-01-11 15:30:43 +00:00
) ;
}
return null ;
2025-12-03 21:56:50 +00:00
}