import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
ActivityIndicator,
FlatList,
Pressable,
SafeAreaView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
const API_URL = 'https://example.com/api/tasks';
export default function TaskMobileScreen() {
const [tasks, setTasks] = useState([]);
const [query, setQuery] = useState('');
const [tab, setTab] = useState('all');
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [error, setError] = useState('');
const fetchTasks = useCallback(async (mode = 'initial') => {
if (mode === 'initial') setLoading(true);
if (mode === 'refresh') setRefreshing(true);
setError('');
try {
const response = await fetch(API_URL);
if (!response.ok) throw new Error('Impossibile caricare i task');
const data = await response.json();
setTasks(data);
} catch (err) {
setError(err.message || 'Errore di rete');
} finally {
if (mode === 'initial') setLoading(false);
if (mode === 'refresh') setRefreshing(false);
}
}, []);
useEffect(() => {
fetchTasks();
}, [fetchTasks]);
const filteredTasks = useMemo(() => {
return tasks.filter((task) => {
const textMatch =
task.title.toLowerCase().includes(query.toLowerCase()) ||
task.client.toLowerCase().includes(query.toLowerCase());
const tabMatch = tab === 'all' ? true : task.status === tab;
return textMatch && tabMatch;
});
}, [tasks, query, tab]);
const summary = useMemo(() => {
return {
total: tasks.length,
todo: tasks.filter((task) => task.status === 'todo').length,
doing: tasks.filter((task) => task.status === 'doing').length,
done: tasks.filter((task) => task.status === 'done').length,
};
}, [tasks]);
function toggleTask(id) {
setTasks((current) =>
current.map((task) =>
task.id === id
? {
...task,
status: task.status === 'done' ? 'doing' : 'done',
}
: task
)
);
}
function renderTab(label, value) {
const active = tab === value;
return (
<Pressable
key={value}
style={[styles.tabButton, active && styles.tabButtonActive]}
onPress={() => setTab(value)}
>
<Text style={[styles.tabText, active && styles.tabTextActive]}>
{label}
</Text>
</Pressable>
);
}
function renderItem({ item }) {
return (
<View style={styles.card}>
<View style={styles.cardTop}>
<View>
<Text style={styles.cardTitle}>{item.title}</Text>
<Text style={styles.cardClient}>{item.client}</Text>
</View>
<Text style={styles.cardStatus}>{item.status}</Text>
</View>
<Text style={styles.cardMeta}>Priority: {item.priority}</Text>
<Text style={styles.cardMeta}>Deadline: {item.deadline}</Text>
<Pressable
style={styles.primaryButton}
onPress={() => toggleTask(item.id)}
>
<Text style={styles.primaryButtonText}>Aggiorna stato</Text>
</Pressable>
</View>
);
}
if (loading) {
return (
<SafeAreaView style={styles.centered}>
<ActivityIndicator size="large" />
<Text style={styles.loadingText}>Caricamento task...</Text>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>Mobile Task Board</Text>
<View style={styles.summaryRow}>
<Text style={styles.summaryText}>Totale: {summary.total}</Text>
<Text style={styles.summaryText}>To do: {summary.todo}</Text>
<Text style={styles.summaryText}>Doing: {summary.doing}</Text>
<Text style={styles.summaryText}>Done: {summary.done}</Text>
</View>
<TextInput
value={query}
onChangeText={setQuery}
placeholder="Cerca task o cliente"
style={styles.searchInput}
/>
<View style={styles.tabsRow}>
{renderTab('Tutti', 'all')}
{renderTab('To do', 'todo')}
{renderTab('Doing', 'doing')}
{renderTab('Done', 'done')}
</View>
{error ? <Text style={styles.errorText}>{error}</Text> : null}
<FlatList
data={filteredTasks}
keyExtractor={(item) => String(item.id)}
renderItem={renderItem}
refreshing={refreshing}
onRefresh={() => fetchTasks('refresh')}
contentContainerStyle={styles.listContent}
ListEmptyComponent={
<Text style={styles.emptyText}>Nessun task trovato</Text>
}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 },
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
loadingText: { marginTop: 12, fontSize: 16 },
title: { fontSize: 28, fontWeight: '700', marginBottom: 16 },
summaryRow: { flexDirection: 'row', flexWrap: 'wrap', marginBottom: 12 },
summaryText: { marginRight: 12, marginBottom: 8, fontSize: 14 },
searchInput: {
borderWidth: 1,
borderColor: '#D0D5DD',
borderRadius: 10,
paddingHorizontal: 12,
paddingVertical: 10,
marginBottom: 12,
},
tabsRow: { flexDirection: 'row', marginBottom: 12 },
tabButton: {
paddingVertical: 8,
paddingHorizontal: 12,
borderRadius: 999,
marginRight: 8,
borderWidth: 1,
borderColor: '#D0D5DD',
},
tabButtonActive: { backgroundColor: '#111827', borderColor: '#111827' },
tabText: { fontSize: 14, color: '#111827' },
tabTextActive: { color: '#FFFFFF' },
errorText: { color: '#B42318', marginBottom: 12 },
listContent: { paddingBottom: 24 },
emptyText: { textAlign: 'center', marginTop: 40, fontSize: 15 },
card: {
borderWidth: 1,
borderColor: '#EAECF0',
borderRadius: 14,
padding: 14,
marginBottom: 12,
},
cardTop: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 8,
},
cardTitle: { fontSize: 18, fontWeight: '600' },
cardClient: { fontSize: 14, marginTop: 2 },
cardStatus: { fontSize: 13, fontWeight: '700' },
cardMeta: { fontSize: 14, marginBottom: 4 },
primaryButton: {
marginTop: 10,
paddingVertical: 10,
borderRadius: 10,
alignItems: 'center',
backgroundColor: '#111827',
},
primaryButtonText: { color: '#FFFFFF', fontWeight: '600' },
});
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
ActivityIndicator,
FlatList,
Pressable,
SafeAreaView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
const API_URL = 'https://example.com/api/tasks';
export default function TaskMobileScreen() {
const [tasks, setTasks] = useState([]);
const [query, setQuery] = useState('');
const [tab, setTab] = useState('all');
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [error, setError] = useState('');
const fetchTasks = useCallback(async (mode = 'initial') => {
if (mode === 'initial') setLoading(true);
if (mode === 'refresh') setRefreshing(true);
setError('');
try {
const response = await fetch(API_URL);
if (!response.ok) throw new Error('Impossibile caricare i task');
const data = await response.json();
setTasks(data);
} catch (err) {
setError(err.message || 'Errore di rete');
} finally {
if (mode === 'initial') setLoading(false);
if (mode === 'refresh') setRefreshing(false);
}
}, []);
useEffect(() => {
fetchTasks();
}, [fetchTasks]);
const filteredTasks = useMemo(() => {
return tasks.filter((task) => {
const textMatch =
task.title.toLowerCase().includes(query.toLowerCase()) ||
task.client.toLowerCase().includes(query.toLowerCase());
const tabMatch = tab === 'all' ? true : task.status === tab;
return textMatch && tabMatch;
});
}, [tasks, query, tab]);
const summary = useMemo(() => {
return {
total: tasks.length,
todo: tasks.filter((task) => task.status === 'todo').length,
doing: tasks.filter((task) => task.status === 'doing').length,
done: tasks.filter((task) => task.status === 'done').length,
};
}, [tasks]);
function toggleTask(id) {
setTasks((current) =>
current.map((task) =>
task.id === id
? {
...task,
status: task.status === 'done' ? 'doing' : 'done',
}
: task
)
);
}
function renderTab(label, value) {
const active = tab === value;
return (
<Pressable
key={value}
style={[styles.tabButton, active && styles.tabButtonActive]}
onPress={() => setTab(value)}
>
<Text style={[styles.tabText, active && styles.tabTextActive]}>
{label}
</Text>
</Pressable>
);
}
function renderItem({ item }) {
return (
<View style={styles.card}>
<View style={styles.cardTop}>
<View>
<Text style={styles.cardTitle}>{item.title}</Text>
<Text style={styles.cardClient}>{item.client}</Text>
</View>
<Text style={styles.cardStatus}>{item.status}</Text>
</View>
<Text style={styles.cardMeta}>Priority: {item.priority}</Text>
<Text style={styles.cardMeta}>Deadline: {item.deadline}</Text>
<Pressable
style={styles.primaryButton}
onPress={() => toggleTask(item.id)}
>
<Text style={styles.primaryButtonText}>Aggiorna stato</Text>
</Pressable>
</View>
);
}
if (loading) {
return (
<SafeAreaView style={styles.centered}>
<ActivityIndicator size="large" />
<Text style={styles.loadingText}>Caricamento task...</Text>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>Mobile Task Board</Text>
<View style={styles.summaryRow}>
<Text style={styles.summaryText}>Totale: {summary.total}</Text>
<Text style={styles.summaryText}>To do: {summary.todo}</Text>
<Text style={styles.summaryText}>Doing: {summary.doing}</Text>
<Text style={styles.summaryText}>Done: {summary.done}</Text>
</View>
<TextInput
value={query}
onChangeText={setQuery}
placeholder="Cerca task o cliente"
style={styles.searchInput}
/>
<View style={styles.tabsRow}>
{renderTab('Tutti', 'all')}
{renderTab('To do', 'todo')}
{renderTab('Doing', 'doing')}
{renderTab('Done', 'done')}
</View>
{error ? <Text style={styles.errorText}>{error}</Text> : null}
<FlatList
data={filteredTasks}
keyExtractor={(item) => String(item.id)}
renderItem={renderItem}
refreshing={refreshing}
onRefresh={() => fetchTasks('refresh')}
contentContainerStyle={styles.listContent}
ListEmptyComponent={
<Text style={styles.emptyText}>Nessun task trovato</Text>
}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 },
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
loadingText: { marginTop: 12, fontSize: 16 },
title: { fontSize: 28, fontWeight: '700', marginBottom: 16 },
summaryRow: { flexDirection: 'row', flexWrap: 'wrap', marginBottom: 12 },
summaryText: { marginRight: 12, marginBottom: 8, fontSize: 14 },
searchInput: {
borderWidth: 1,
borderColor: '#D0D5DD',
borderRadius: 10,
paddingHorizontal: 12,
paddingVertical: 10,
marginBottom: 12,
},
tabsRow: { flexDirection: 'row', marginBottom: 12 },
tabButton: {
paddingVertical: 8,
paddingHorizontal: 12,
borderRadius: 999,
marginRight: 8,
borderWidth: 1,
borderColor: '#D0D5DD',
},
tabButtonActive: { backgroundColor: '#111827', borderColor: '#111827' },
tabText: { fontSize: 14, color: '#111827' },
tabTextActive: { color: '#FFFFFF' },
errorText: { color: '#B42318', marginBottom: 12 },
listContent: { paddingBottom: 24 },
emptyText: { textAlign: 'center', marginTop: 40, fontSize: 15 },
card: {
borderWidth: 1,
borderColor: '#EAECF0',
borderRadius: 14,
padding: 14,
marginBottom: 12,
},
cardTop: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 8,
},
cardTitle: { fontSize: 18, fontWeight: '600' },
cardClient: { fontSize: 14, marginTop: 2 },
cardStatus: { fontSize: 13, fontWeight: '700' },
cardMeta: { fontSize: 14, marginBottom: 4 },
primaryButton: {
marginTop: 10,
paddingVertical: 10,
borderRadius: 10,
alignItems: 'center',
backgroundColor: '#111827',
},
primaryButtonText: { color: '#FFFFFF', fontWeight: '600' },
});