Aplikasi memiliki beberapa fitur utama, yaitu menambahkan tugas baru, menampilkan daftar tugas, mengedit tugas, menghapus tugas, serta menampilkan jumlah total tugas dan jumlah tugas yang telah diselesaikan. Seluruh data tugas ditampilkan menggunakan komponen FlatList, sehingga daftar tugas dapat ditampilkan dengan rapi dan efisien.
Secara keseluruhan, aplikasi Todo List ini berhasil memenuhi fungsi utamanya sebagai media untuk mengelola daftar tugas. Meskipun masih sederhana, proyek ini menjadi langkah awal yang baik untuk memahami pengembangan aplikasi mobile menggunakan Node.js dan React Native, serta dapat dikembangkan lebih lanjut dengan fitur seperti penyimpanan database, pengingat tugas, dan autentikasi pengguna.
import React, { useState } from 'react';
import {
StyleSheet,
Text,
View,
TextInput,
TouchableOpacity,
FlatList,
Keyboard,
Alert,
} from 'react-native';
import { StatusBar } from 'expo-status-bar';
export default function App() {
const [task, setTask] = useState('');
const [tasks, setTasks] = useState([
{ id: '1', text: '📚 Belajar React Native', completed: false },
{ id: '2', text: '💻 Buat Aplikasi Todo List', completed: false },
{ id: '3', text: '🎯 Selesaikan Project', completed: false },
]);
const [editingId, setEditingId] = useState(null);
// Fungsi untuk menambah tugas
const addTask = () => {
if (task.trim().length === 0) {
Alert.alert('⚠️', 'Tugas tidak boleh kosong!');
return;
}
if (editingId) {
// Mode Edit
setTasks(
tasks.map((item) =>
item.id === editingId ? { ...item, text: task.trim() } : item
)
);
setEditingId(null);
} else {
// Mode Tambah
const newTask = {
id: Date.now().toString(),
text: task.trim(),
completed: false,
};
setTasks([...tasks, newTask]);
}
setTask('');
Keyboard.dismiss();
};
// Fungsi untuk menghapus tugas
const deleteTask = (id) => {
Alert.alert(
'🗑️ Hapus Tugas',
'Yakin mau hapus tugas ini?',
[
{ text: 'Batal', style: 'cancel' },
{
text: 'Hapus',
style: 'destructive',
onPress: () => setTasks(tasks.filter((item) => item.id !== id)),
},
]
);
};
// Fungsi untuk toggle selesai/belum
const toggleComplete = (id) => {
setTasks(
tasks.map((item) =>
item.id === id ? { ...item, completed: !item.completed } : item
)
);
};
// Fungsi untuk edit
const startEdit = (item) => {
setTask(item.text);
setEditingId(item.id);
};
// Render item tugas
const renderTask = ({ item }) => (
<TouchableOpacity
style={[
styles.taskItem,
item.completed && styles.taskItemCompleted,
]}
onPress={() => toggleComplete(item.id)}
activeOpacity={0.7}
>
<View style={styles.taskLeft}>
<Text style={[styles.taskText, item.completed && styles.taskTextCompleted]}>
{item.text}
</Text>
</View>
<View style={styles.taskRight}>
{!item.completed && (
<TouchableOpacity
onPress={() => startEdit(item)}
style={styles.editButton}
>
<Text style={styles.editText}>✏️</Text>
</TouchableOpacity>
)}
<TouchableOpacity
onPress={() => deleteTask(item.id)}
style={styles.deleteButton}
>
<Text style={styles.deleteText}>🗑️</Text>
</TouchableOpacity>
</View>
</TouchableOpacity>
);
// Statistik
const totalTasks = tasks.length;
const completedTasks = tasks.filter((item) => item.completed).length;
return (
<View style={styles.container}>
<StatusBar style="light" />
{/* Header */}
<View style={styles.header}>
<Text style={styles.headerTitle}>📋 Todo List</Text>
<Text style={styles.headerSubtitle}>
{completedTasks} dari {totalTasks} selesai
</Text>
<View style={styles.progressBar}>
<View
style={[
styles.progressFill,
{ width: totalTasks > 0 ? `${(completedTasks / totalTasks) * 100}%` : '0%' },
]}
/>
</View>
</View>
{/* Form Input */}
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
placeholder="✍️ Tambahkan tugas..."
placeholderTextColor="#999"
value={task}
onChangeText={setTask}
onSubmitEditing={addTask}
/>
<TouchableOpacity
style={[styles.addButton, !task.trim() && styles.addButtonDisabled]}
onPress={addTask}
disabled={!task.trim()}
>
<Text style={styles.addButtonText}>
{editingId ? '🔄 Update' : '➕ Tambah'}
</Text>
</TouchableOpacity>
</View>
{/* Daftar Tugas */}
{tasks.length === 0 ? (
<View style={styles.emptyContainer}>
<Text style={styles.emptyEmoji}>🎉</Text>
<Text style={styles.emptyText}>Belum ada tugas!</Text>
<Text style={styles.emptySubText}>Tambahkan tugas pertama kamu</Text>
</View>
) : (
<FlatList
data={tasks}
renderItem={renderTask}
keyExtractor={(item) => item.id}
contentContainerStyle={styles.listContainer}
showsVerticalScrollIndicator={false}
/>
)}
{/* Footer */}
<View style={styles.footer}>
<Text style={styles.footerText}>
{totalTasks} tugas • {completedTasks} selesai
</Text>
</View>
</View>
);
}
// ==================== STYLING ====================
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F7FA',
paddingTop: 50,
},
// HEADER
header: {
paddingHorizontal: 20,
paddingBottom: 20,
backgroundColor: '#6C63FF',
borderBottomLeftRadius: 30,
borderBottomRightRadius: 30,
paddingTop: 20,
},
headerTitle: {
fontSize: 28,
fontWeight: 'bold',
color: '#fff',
marginBottom: 5,
},
headerSubtitle: {
fontSize: 14,
color: 'rgba(255,255,255,0.8)',
marginBottom: 10,
},
progressBar: {
height: 6,
backgroundColor: 'rgba(255,255,255,0.3)',
borderRadius: 10,
overflow: 'hidden',
},
progressFill: {
height: '100%',
backgroundColor: '#FFD700',
borderRadius: 10,
},
// INPUT
inputContainer: {
flexDirection: 'row',
paddingHorizontal: 20,
marginTop: -20,
marginBottom: 15,
},
input: {
flex: 1,
backgroundColor: '#fff',
paddingHorizontal: 18,
paddingVertical: 14,
borderRadius: 15,
fontSize: 16,
color: '#333',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
addButton: {
backgroundColor: '#6C63FF',
paddingHorizontal: 20,
paddingVertical: 14,
borderRadius: 15,
marginLeft: 10,
justifyContent: 'center',
shadowColor: '#6C63FF',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
elevation: 3,
},
addButtonDisabled: {
backgroundColor: '#A9A9A9',
shadowOpacity: 0,
},
addButtonText: {
color: '#fff',
fontWeight: 'bold',
fontSize: 14,
},
// LIST
listContainer: {
paddingHorizontal: 20,
paddingBottom: 100,
},
// TASK ITEM
taskItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '#fff',
padding: 16,
borderRadius: 15,
marginBottom: 10,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.08,
shadowRadius: 4,
elevation: 2,
},
taskItemCompleted: {
backgroundColor: '#E8F5E9',
opacity: 0.8,
},
taskLeft: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
},
taskText: {
fontSize: 16,
color: '#333',
flex: 1,
},
taskTextCompleted: {
textDecorationLine: 'line-through',
color: '#888',
},
taskRight: {
flexDirection: 'row',
alignItems: 'center',
},
editButton: {
marginRight: 10,
padding: 5,
},
editText: {
fontSize: 18,
},
deleteButton: {
padding: 5,
},
deleteText: {
fontSize: 18,
},
// EMPTY STATE
emptyContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 20,
},
emptyEmoji: {
fontSize: 60,
marginBottom: 15,
},
emptyText: {
fontSize: 20,
fontWeight: 'bold',
color: '#333',
},
emptySubText: {
fontSize: 14,
color: '#999',
marginTop: 5,
},
// FOOTER
footer: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
backgroundColor: '#fff',
paddingVertical: 12,
alignItems: 'center',
borderTopWidth: 1,
borderTopColor: '#eee',
},
footerText: {
color: '#888',
fontSize: 12,
},
});
Komentar
Posting Komentar