[V0.4] Added TODOs

This commit is contained in:
c-d-p
2025-04-21 23:47:38 +02:00
parent 5df6ae35cc
commit c0a58b45f4
26 changed files with 589 additions and 17 deletions

View File

@@ -1,20 +1,220 @@
// src/screens/DashboardScreen.tsx
import React, { useState, useEffect } from 'react'; // Added useEffect
import { View, StyleSheet, ScrollView } from 'react-native'; // Added ScrollView
import { Text, TextInput, Button, useTheme, Card, List, Divider } from 'react-native-paper'; // Added Card, List, Divider
import React, { useState, useEffect, useCallback } from 'react'; // Added useCallback
import { View, StyleSheet, ScrollView, TouchableOpacity } from 'react-native'; // Added TouchableOpacity
import { Text, TextInput, Button, useTheme, Card, List, Divider, Checkbox, IconButton, ActivityIndicator } from 'react-native-paper'; // Added Checkbox, IconButton, ActivityIndicator
import { useNavigation } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { format, addDays, startOfDay, isSameDay, parseISO, endOfDay } from 'date-fns'; // Added date-fns imports
import { getCalendarEvents } from '../api/calendar';
import { CalendarEvent } from '../types/calendar';
import { Todo, TodoCreate, TodoUpdate } from '../types/todo'; // Import TODO types
import { getTodos, createTodo, updateTodo, deleteTodo } from '../api/todo'; // Import TODO API functions
// Placeholder for the TODO component
const TodoComponent = () => (
<View style={{ marginVertical: 10, padding: 10, borderWidth: 1, borderColor: 'grey' }}>
<Text>TODO Component Placeholder</Text>
</View>
);
// --- TODO Component Implementation ---
const TodoComponent = () => {
const theme = useTheme();
const [todos, setTodos] = useState<Todo[]>([]);
const [newTask, setNewTask] = useState('');
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchTodos = useCallback(async () => {
setLoading(true);
setError(null);
try {
const fetchedTodos = await getTodos();
// Add explicit types for sort parameters
setTodos(fetchedTodos.sort((a: Todo, b: Todo) => a.id - b.id));
} catch (err) {
console.error("Failed to fetch todos:", err);
setError("Failed to load TODOs.");
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchTodos();
}, [fetchTodos]);
const handleAddTask = async () => {
const trimmedTask = newTask.trim();
if (!trimmedTask) return;
const newTodoData: TodoCreate = { task: trimmedTask };
try {
const createdTodo = await createTodo(newTodoData);
setTodos(prevTodos => [...prevTodos, createdTodo]);
setNewTask(''); // Clear input
} catch (err) {
console.error("Failed to add todo:", err);
setError("Failed to add TODO."); // Show error feedback
}
};
const handleToggleComplete = async (todo: Todo) => {
const updatedTodoData: TodoUpdate = { complete: !todo.complete };
try {
const updatedTodo = await updateTodo(todo.id, updatedTodoData);
setTodos(prevTodos =>
prevTodos.map(t => (t.id === todo.id ? updatedTodo : t))
);
} catch (err) {
console.error("Failed to update todo:", err);
setError("Failed to update TODO status.");
}
};
const handleDeleteTask = async (id: number) => {
try {
await deleteTodo(id);
setTodos(prevTodos => prevTodos.filter(t => t.id !== id));
} catch (err) {
console.error("Failed to delete todo:", err);
setError("Failed to delete TODO.");
}
};
const styles = StyleSheet.create({
card: {
marginVertical: 8,
backgroundColor: theme.colors.surface, // Use surface color for card background
},
cardTitle: {
fontSize: 18,
fontWeight: 'bold',
paddingLeft: 16,
paddingTop: 12,
paddingBottom: 8,
color: theme.colors.primary,
},
inputContainer: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
paddingBottom: 12,
},
textInput: {
flex: 1,
marginRight: 8,
backgroundColor: theme.colors.background, // Match background
},
listItem: {
paddingVertical: 0, // Reduced padding
paddingLeft: 8, // Adjust left padding
backgroundColor: theme.colors.surface, // Ensure item background matches card
minHeight: 48,
},
listCheckboxContainer: {
justifyContent: 'center',
height: '100%',
marginRight: 8,
},
listItemContent: {
marginLeft: -8, // Counteract default List.Item padding if needed
},
taskText: {
fontSize: 15,
color: theme.colors.onSurface,
},
completedTaskText: {
textDecorationLine: 'line-through',
color: theme.colors.onSurfaceDisabled,
},
deleteButton: {
marginRight: -8, // Align delete button better
},
loadingContainer: {
padding: 20,
alignItems: 'center',
justifyContent: 'center',
},
errorText: {
paddingHorizontal: 16,
paddingBottom: 12,
color: theme.colors.error,
},
divider: {
marginHorizontal: 16,
}
});
return (
<Card style={styles.card} elevation={1}>
<Card.Content style={{ paddingHorizontal: 0, paddingVertical: 0 }}>
<Text style={styles.cardTitle}>TODO List</Text>
{/* Add New Task Input */}
<View style={styles.inputContainer}>
<TextInput
label="New Task"
value={newTask}
onChangeText={setNewTask}
mode="outlined"
style={styles.textInput}
dense // Make input smaller
onSubmitEditing={handleAddTask} // Add task on submit
/>
<Button mode="contained" onPress={handleAddTask} disabled={!newTask.trim()}>
Add
</Button>
</View>
{/* Loading Indicator */}
{loading && (
<View style={styles.loadingContainer}>
<ActivityIndicator animating={true} color={theme.colors.primary} />
</View>
)}
{/* Error Message */}
{error && !loading && <Text style={styles.errorText}>{error}</Text>}
{/* TODO List */}
{!loading && !error && todos.length === 0 && (
<Text style={{ paddingHorizontal: 16, paddingBottom: 12, fontStyle: 'italic', color: theme.colors.onSurfaceDisabled }}>No tasks yet. Add one above!</Text>
)}
{!loading && !error && todos.map((todo, index) => (
<React.Fragment key={todo.id}>
<List.Item
style={styles.listItem} // Apply the style with alignItems: 'center'
title={
<Text style={[styles.taskText, todo.complete && styles.completedTaskText]}>
{todo.task}
</Text>
}
titleNumberOfLines={2} // Allow wrapping
left={props => (
<View {...props} style={styles.listCheckboxContainer}>
<Checkbox
{...props}
status={todo.complete ? 'checked' : 'unchecked'}
onPress={() => handleToggleComplete(todo)}
color={theme.colors.primary}
/>
</View>
)}
right={props => (
<IconButton
{...props}
icon="delete-outline"
onPress={() => handleDeleteTask(todo.id)}
iconColor={theme.colors.error}
style={styles.deleteButton}
size={20}
/>
)}
contentStyle={styles.listItemContent}
/>
{index < todos.length - 1 && <Divider style={styles.divider} />}
</React.Fragment>
))}
</Card.Content>
</Card>
);
};
// --- End TODO Component ---
// --- Calendar Preview Component Implementation ---
const CalendarPreview = () => {