Add offline mode components and enhance synchronization logic
- Introduced `OfflineSyncManager`, `OfflineToggle`, `OfflinePinSetup`, `OfflineIndicator`, and `OfflineSyncDetails` components to support offline mode functionality. - Added PIN verification (`OfflinePinVerify`) for secure offline access. - Implemented sync options for books (current, recent, all) with progress tracking and improved user feedback. - Enhanced offline context integration and error handling in sync processes. - Refined UI elements for better online/offline status visibility and manual sync controls.
This commit is contained in:
174
components/offline/OfflineIndicator.tsx
Normal file
174
components/offline/OfflineIndicator.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
'use client';
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import OfflineContext from '@/context/OfflineContext';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faWifi, faWifiSlash, faSync, faCircle, faExclamationTriangle, faCheck } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
/**
|
||||
* OfflineIndicator - Displays current online/offline status and sync progress
|
||||
* Allows user to toggle manual offline mode
|
||||
*/
|
||||
export default function OfflineIndicator() {
|
||||
const { offlineMode, toggleOfflineMode, syncNow } = useContext(OfflineContext);
|
||||
|
||||
const {
|
||||
isOffline,
|
||||
isManuallyOffline,
|
||||
isNetworkOnline,
|
||||
isDatabaseInitialized,
|
||||
syncProgress,
|
||||
lastSyncAt,
|
||||
error
|
||||
} = offlineMode;
|
||||
|
||||
// Determine status color and text
|
||||
const getStatusInfo = () => {
|
||||
if (!isDatabaseInitialized) {
|
||||
return {
|
||||
color: 'text-gray-400',
|
||||
bgColor: 'bg-gray-100',
|
||||
icon: faCircle,
|
||||
text: 'DB non initialisée'
|
||||
};
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
color: 'text-red-500',
|
||||
bgColor: 'bg-red-50',
|
||||
icon: faExclamationTriangle,
|
||||
text: 'Erreur de sync'
|
||||
};
|
||||
}
|
||||
|
||||
if (syncProgress.isSyncing) {
|
||||
return {
|
||||
color: 'text-blue-500',
|
||||
bgColor: 'bg-blue-50',
|
||||
icon: faSync,
|
||||
text: 'Synchronisation...',
|
||||
spin: true
|
||||
};
|
||||
}
|
||||
|
||||
if (isOffline) {
|
||||
if (syncProgress.pendingChanges > 0) {
|
||||
return {
|
||||
color: 'text-orange-500',
|
||||
bgColor: 'bg-orange-50',
|
||||
icon: faWifiSlash,
|
||||
text: `Hors ligne (${syncProgress.pendingChanges} en attente)`
|
||||
};
|
||||
}
|
||||
return {
|
||||
color: 'text-orange-500',
|
||||
bgColor: 'bg-orange-50',
|
||||
icon: faWifiSlash,
|
||||
text: isManuallyOffline ? 'Mode hors ligne' : 'Hors ligne'
|
||||
};
|
||||
}
|
||||
|
||||
if (syncProgress.pendingChanges > 0) {
|
||||
return {
|
||||
color: 'text-yellow-500',
|
||||
bgColor: 'bg-yellow-50',
|
||||
icon: faSync,
|
||||
text: `${syncProgress.pendingChanges} changements à sync`
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
color: 'text-green-500',
|
||||
bgColor: 'bg-green-50',
|
||||
icon: faCheck,
|
||||
text: 'Synchronisé'
|
||||
};
|
||||
};
|
||||
|
||||
const statusInfo = getStatusInfo();
|
||||
|
||||
// Format last sync time
|
||||
const formatLastSync = () => {
|
||||
if (!lastSyncAt) return 'Jamais';
|
||||
|
||||
const diff = Date.now() - lastSyncAt;
|
||||
const minutes = Math.floor(diff / 60000);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
|
||||
if (hours > 0) return `Il y a ${hours}h`;
|
||||
if (minutes > 0) return `Il y a ${minutes}min`;
|
||||
return 'À l\'instant';
|
||||
};
|
||||
|
||||
const handleSyncNow = async () => {
|
||||
if (!isOffline && !syncProgress.isSyncing) {
|
||||
await syncNow();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3 px-4 py-2 bg-white border-b border-gray-200">
|
||||
{/* Status indicator */}
|
||||
<div className={`flex items-center gap-2 px-3 py-1 rounded-full ${statusInfo.bgColor}`}>
|
||||
<FontAwesomeIcon
|
||||
icon={statusInfo.icon}
|
||||
className={`${statusInfo.color} text-sm`}
|
||||
spin={statusInfo.spin}
|
||||
/>
|
||||
<span className={`text-sm font-medium ${statusInfo.color}`}>
|
||||
{statusInfo.text}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Last sync time */}
|
||||
{isDatabaseInitialized && !isOffline && (
|
||||
<span className="text-xs text-gray-500">
|
||||
Dernière sync: {formatLastSync()}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Error message */}
|
||||
{error && (
|
||||
<span className="text-xs text-red-500 max-w-xs truncate" title={error}>
|
||||
{error}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
{/* Manual sync button */}
|
||||
{isDatabaseInitialized && !isOffline && !syncProgress.isSyncing && (
|
||||
<button
|
||||
onClick={handleSyncNow}
|
||||
className="px-3 py-1 text-sm text-blue-600 hover:bg-blue-50 rounded transition-colors"
|
||||
title="Synchroniser maintenant"
|
||||
>
|
||||
<FontAwesomeIcon icon={faSync} className="mr-1" />
|
||||
Sync
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Offline toggle */}
|
||||
{isDatabaseInitialized && (
|
||||
<button
|
||||
onClick={toggleOfflineMode}
|
||||
className={`px-3 py-1 text-sm rounded transition-colors ${
|
||||
isManuallyOffline
|
||||
? 'bg-orange-100 text-orange-700 hover:bg-orange-200'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
||||
}`}
|
||||
title={isManuallyOffline ? 'Passer en ligne' : 'Passer hors ligne'}
|
||||
disabled={!isNetworkOnline && !isManuallyOffline}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isOffline ? faWifiSlash : faWifi}
|
||||
className="mr-1"
|
||||
/>
|
||||
{isManuallyOffline ? 'En ligne' : 'Hors ligne'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user