[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

@@ -108,11 +108,9 @@ apiClient.interceptors.response.use(
}
console.log('[API Client] Attempting token refresh...');
// Send refresh token in the body, remove withCredentials
const refreshResponse = await apiClient.post('/auth/refresh',
{ refresh_token: storedRefreshToken }, // Send token in body
{
// No withCredentials needed
headers: { 'Content-Type': 'application/json' },
}
);

View File

@@ -0,0 +1,58 @@
// interfaces/nativeapp/src/api/todo.ts
import apiClient from './client';
import { Todo, TodoCreate, TodoUpdate } from '../types/todo';
export const getTodos = async (skip: number = 0, limit: number = 100): Promise<Todo[]> => {
try {
const response = await apiClient.get('/todos/', { params: { skip, limit } });
console.log("[TODO] Got todos:", response.data);
return response.data;
} catch (error) {
console.error("Error fetching todos", error);
throw error;
}
};
export const getTodoById = async (todo_id: number): Promise<Todo> => {
try {
const response = await apiClient.get(`/todos/${todo_id}`);
console.log("[TODO] Got todo:", response.data);
return response.data;
} catch (error) {
console.error(`Error fetching todo ${todo_id}`, error);
throw error;
}
};
export const createTodo = async (todo: TodoCreate): Promise<Todo> => {
try {
const response = await apiClient.post('/todos/', todo);
console.log("[TODO] Created todo:", response.data);
return response.data;
} catch (error) {
console.error("Error creating todo", error);
throw error;
}
};
export const updateTodo = async (todo_id: number, todo: TodoUpdate): Promise<Todo> => {
try {
const response = await apiClient.put(`/todos/${todo_id}`, todo);
console.log("[TODO] Updated todo:", response.data);
return response.data;
} catch (error) {
console.error(`Error updating todo ${todo_id}`, error);
throw error;
}
};
export const deleteTodo = async (todo_id: number): Promise<Todo> => { // Backend returns the deleted item
try {
const response = await apiClient.delete(`/todos/${todo_id}`);
console.log("[TODO] Deleted todo:", response.data);
return response.data; // Return the data which is the deleted todo object
} catch (error) {
console.error(`Error deleting todo ${todo_id}`, error);
throw error;
}
};

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 = () => {

View File

@@ -0,0 +1,23 @@
// interfaces/nativeapp/src/types/todo.ts
export interface Todo {
id: number;
task: string;
date?: string | null; // Assuming date comes as ISO string or null
remind: boolean;
complete: boolean;
owner_id: number;
}
export interface TodoCreate {
task: string;
date?: string | null;
remind?: boolean;
complete?: boolean; // Usually not set on creation, defaults to false
}
export interface TodoUpdate {
task?: string;
date?: string | null;
remind?: boolean;
complete?: boolean;
}