- 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.
175 lines
5.9 KiB
TypeScript
175 lines
5.9 KiB
TypeScript
'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>
|
|
);
|
|
}
|