import React, { useMemo, useCallback } from 'react'; import { View, Text, StyleSheet, FlatList, TouchableOpacity, ActivityIndicator, Alert, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { MaterialIcons } from '@expo/vector-icons'; import { fileStore, FileRecord } from '../services/fileStore'; import { safDirectory } from '../services/safDirectory'; import { useSyncQueue } from '../hooks/useSyncQueue'; import { useAutoSync } from '../hooks/useAutoSync'; import { useSyncPush } from '../hooks/useSyncPush'; import { useUploadQueue } from '../hooks/useUploadQueue'; import { UploadTask } from '../services/uploadQueue'; function formatSize(bytes: number): string { if (bytes === 0) return ''; if (bytes < 1024) return `${bytes} o`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} Ko`; return `${(bytes / (1024 * 1024)).toFixed(1)} Mo`; } function uploadStatusIcon(task: UploadTask) { switch (task.status) { case 'pending': return ; case 'uploading': return ; case 'done': return ; case 'error': return ; } } export function SyncDetailScreen() { const insets = useSafeAreaInsets(); const { pendingCount, isSyncing, refresh } = useSyncQueue(); const { triggerSync } = useAutoSync(); const { push } = useSyncPush(); const { tasks: uploadTasks, retry, retryAll } = useUploadQueue(); const pendingFiles = useMemo(() => { return fileStore.getPendingSync(); }, []); const handleSyncAll = useCallback(async () => { await triggerSync(); try { const { getStoredDeviceServerId } = await import('../hooks/useDeviceRegistration'); const serverId = await getStoredDeviceServerId(); if (serverId) { await push(serverId); } } catch { } refresh(); }, [triggerSync, push, refresh]); const handleTaskPress = useCallback((task: UploadTask) => { if (task.status !== 'error') return; Alert.alert( 'Erreur d\'upload', task.error || 'Erreur inconnue', [ { text: 'Annuler', style: 'cancel' }, { text: 'Réessayer', onPress: () => retry(task.id) }, ], ); }, [retry]); const hasUploads = uploadTasks.length > 0; const hasPending = pendingFiles.length > 0; const hasContent = hasUploads || hasPending; const uploadErrors = uploadTasks.filter((t) => t.status === 'error'); return ( {(hasPending || uploadErrors.length > 0) && ( 0 && hasPending ? retryAll : handleSyncAll} disabled={isSyncing} > {isSyncing ? ( ) : ( 0 ? "refresh" : "cloud-upload"} size={20} color="#fff" /> )} {isSyncing ? 'Synchronisation...' : uploadErrors.length > 0 && hasPending ? 'Tout réessayer' : hasPending ? `Synchroniser (${pendingCount})` : `Réessayer (${uploadErrors.length})`} )} {!hasContent ? ( Tout est synchronisé Aucun fichier en attente ) : ( ({ type: 'upload' as const, data: t })), ...(hasPending ? [{ type: 'section', label: 'Fichiers locaux à synchroniser' } as const] : []), ...pendingFiles.map((f) => ({ type: 'file' as const, data: f })), ]} keyExtractor={(item) => item.type === 'section' ? item.label : item.type === 'upload' ? item.data.id : item.data.id } renderItem={({ item }) => { if (item.type === 'section') { return {item.label}; } if (item.type === 'upload') { const task = item.data; return ( handleTaskPress(task)} activeOpacity={task.status === 'error' ? 0.6 : 1} > {uploadStatusIcon(task)} {task.file.name} {task.status === 'uploading' && ( )} {task.status === 'error' && task.error && ( {task.error} )} {task.status === 'done' && ( Upload terminé )} {task.status === 'pending' && ( En attente )} ); } const file = item.data; return ( {file.name} {formatSize(file.size)} {file.mimeType ? ` · ${file.mimeType.split('/').pop()}` : ''} ); }} contentContainerStyle={styles.list} /> )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', }, syncBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: '#1976D2', marginHorizontal: 16, marginTop: 16, borderRadius: 10, paddingVertical: 14, gap: 10, }, syncBtnDisabled: { backgroundColor: '#90CAF9', }, syncBtnText: { fontSize: 16, color: '#fff', fontWeight: '600', }, list: { padding: 16, }, headerText: { fontSize: 14, color: '#666', marginBottom: 12, }, fileRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#fff', borderRadius: 10, padding: 12, marginBottom: 8, gap: 12, }, fileInfo: { flex: 1, }, fileName: { fontSize: 15, color: '#333', fontWeight: '500', }, fileMeta: { fontSize: 12, color: '#999', marginTop: 2, }, localBadge: { width: 28, height: 28, borderRadius: 14, backgroundColor: '#f5f5f5', justifyContent: 'center', alignItems: 'center', }, fileRowError: { backgroundColor: '#FFF0F0', }, progressBar: { height: 4, backgroundColor: '#E0E0E0', borderRadius: 2, marginTop: 6, overflow: 'hidden', }, progressFill: { height: '100%', backgroundColor: '#1976D2', borderRadius: 2, }, errorText: { fontSize: 12, color: '#E53935', marginTop: 2, }, doneText: { fontSize: 12, color: '#4CAF50', marginTop: 2, }, pendingText: { fontSize: 12, color: '#FFA000', marginTop: 2, }, empty: { flex: 1, justifyContent: 'center', alignItems: 'center', gap: 12, }, emptyTitle: { fontSize: 18, fontWeight: '600', color: '#333', }, emptySubtitle: { fontSize: 14, color: '#999', }, });