Files
avante-movil/src/screens/ProjectsScreen.tsx
T

126 lines
3.7 KiB
TypeScript
Raw Normal View History

import { NativeStackScreenProps } from '@react-navigation/native-stack';
import React, { useCallback, useEffect, useState } from 'react';
import {
ActivityIndicator,
FlatList,
RefreshControl,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import * as api from '../api/endpoints';
import { Project } from '../api/types';
import { useSession } from '../auth/session';
import { getCursor, getProjects, saveProjectList } from '../db/repositories';
import { isOnline } from '../net/connectivity';
import { initialPull, runSync } from '../sync/engine';
import { RootStackParamList } from '../navigation/types';
type Props = NativeStackScreenProps<RootStackParamList, 'Projects'>;
function normalize(res: { data?: Project[] } | Project[]): Project[] {
return Array.isArray(res) ? res : res.data ?? [];
}
export function ProjectsScreen({ navigation }: Props) {
const { user, signOut } = useSession();
const [projects, setProjects] = useState<Project[]>([]);
const [loading, setLoading] = useState(false);
const [opening, setOpening] = useState<number | null>(null);
const load = useCallback(async () => {
setLoading(true);
try {
if (await isOnline()) {
const list = normalize(await api.listProjects());
await saveProjectList(list);
}
setProjects(await getProjects());
} catch {
setProjects(await getProjects()); // fallback offline
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
void load();
}, [load]);
const openProject = useCallback(
async (p: Project) => {
setOpening(p.id);
try {
const online = await isOnline();
if (online) {
const cursor = await getCursor(p.id);
if (cursor) await runSync(p.id);
else await initialPull(p.id);
}
navigation.navigate('ProjectDetail', { projectId: p.id, name: p.name });
} finally {
setOpening(null);
}
},
[navigation],
);
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.hello}>Hola, {user?.name ?? ''}</Text>
<TouchableOpacity onPress={signOut}>
<Text style={styles.logout}>Salir</Text>
</TouchableOpacity>
</View>
<FlatList
data={projects}
keyExtractor={(p) => String(p.id)}
refreshControl={<RefreshControl refreshing={loading} onRefresh={load} />}
ListEmptyComponent={
loading ? null : <Text style={styles.empty}>No hay proyectos.</Text>
}
renderItem={({ item }) => (
<TouchableOpacity style={styles.row} onPress={() => openProject(item)}>
<View style={{ flex: 1 }}>
<Text style={styles.name}>{item.name}</Text>
{item.reference ? <Text style={styles.ref}>{item.reference}</Text> : null}
</View>
{opening === item.id ? (
<ActivityIndicator />
) : (
<Text style={styles.chevron}></Text>
)}
</TouchableOpacity>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1 },
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
},
hello: { fontSize: 16, fontWeight: '600' },
logout: { color: '#b00020', fontWeight: '600' },
row: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 14,
borderTopWidth: StyleSheet.hairlineWidth,
borderColor: '#ddd',
},
name: { fontSize: 16, fontWeight: '600' },
ref: { fontSize: 13, color: '#666', marginTop: 2 },
chevron: { fontSize: 24, color: '#999' },
empty: { textAlign: 'center', marginTop: 40, color: '#888' },
});