HotnKen's picture
ROLE: Act as an expert React/TypeScript developer.
0a86e87 verified
tsx
import React, { useState, useEffect } from 'react';
interface Todo {
id: string;
text: string;
deadline: string;
status: 'finished' | 'unfinished';
finishedTime: string | null;
}
const ToDoList: React.FC = () => {
const [todos, setTodos] = useState<Todo[]>([]);
const [inputText, setInputText] = useState('');
const [deadline, setDeadline] = useState('');
const [searchTerm, setSearchTerm] = useState('');
const [filter, setFilter] = useState<'all' | 'finished' | 'unfinished'>('all');
const [sortBy, setSortBy] = useState<'deadline'>('deadline');
// Load todos from localStorage on component mount
useEffect(() => {
const savedTodos = localStorage.getItem('todos');
if (savedTodos) {
setTodos(JSON.parse(savedTodos));
}
}, []);
// Save todos to localStorage whenever they change
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
// Create a new todo
const addTodo = () => {
if (!inputText.trim() || !deadline) return;
const newTodo: Todo = {
id: Date.now().toString(),
text: inputText,
deadline,
status: 'unfinished',
finishedTime: null
};
setTodos([...todos, newTodo]);
setInputText('');
setDeadline('');
};
// Toggle todo status
const toggleTodo = (id: string) => {
setTodos(todos.map(todo => {
if (todo.id === id) {
return {
...todo,
status: todo.status === 'unfinished' ? 'finished' : 'unfinished',
finishedTime: todo.status === 'unfinished' ? new Date().toISOString() : null
};
}
return todo;
}));
};
// Delete a todo
const deleteTodo = (id: string) => {
setTodos(todos.filter(todo => todo.id !== id));
};
// Filter and sort todos
const getFilteredSortedTodos = () => {
let filtered = todos;
// Apply search filter
if (searchTerm) {
filtered = filtered.filter(todo =>
todo.text.toLowerCase().includes(searchTerm.toLowerCase())
);
}
// Apply status filter
if (filter !== 'all') {
filtered = filtered.filter(todo => todo.status === filter);
}
// Apply sorting
filtered.sort((a, b) => {
// Unfinished items come first
if (a.status === 'unfinished' && b.status === 'finished') return -1;
if (a.status === 'finished' && b.status === 'unfinished') return 1;
// Then sort by deadline
return new Date(a.deadline).getTime() - new Date(b.deadline).getTime();
});
return filtered;
};
// Handle form submission
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
addTodo();
};
return (
<div style={{
maxWidth: '800px',
margin: '0 auto',
padding: '20px',
fontFamily: 'sans-serif'
}}>
<h1 style={{
textAlign: 'center',
color: '#333',
marginBottom: '30px',
fontSize: '2.5rem'
}}>Tasky McTaskface</h1>
{/* Add Todo Form */}
<form onSubmit={handleSubmit} style={{
display: 'flex',
flexDirection: 'column',
gap: '10px',
marginBottom: '20px',
padding: '20px',
borderRadius: '8px',
background: '#f5f5f5'
}}>
<input
type="text"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="What needs to be done?"
style={{
padding: '10px',
borderRadius: '4px',
border: '1px solid #ddd',
fontSize: '1rem'
}}
required
/>
<div style={{ display: 'flex', gap: '10px' }}>
<input
type="datetime-local"
value={deadline}
onChange={(e) => setDeadline(e.target.value)}
style={{
padding: '10px',
borderRadius: '4px',
border: '1px solid #ddd',
fontSize: '1rem',
flex: 1
}}
required
/>
<button
type="submit"
style={{
padding: '10px 20px',
borderRadius: '4px',
border: 'none',
background: '#4CAF50',
color: 'white',
cursor: 'pointer',
fontSize: '1rem',
fontWeight: 'bold'
}}
>
Add Task
</button>
</div>
</form>
{/* Controls */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
marginBottom: '20px',
gap: '10px',
flexWrap: 'wrap'
}}>
<input
type="text"
placeholder="Search tasks..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
style={{
padding: '10px',
borderRadius: '4px',
border: '1px solid #ddd',
fontSize: '1rem',
flex: 1,
minWidth: '200px'
}}
/>
<select
value={filter}
onChange={(e) => setFilter(e.target.value as any)}
style={{
padding: '10px',
borderRadius: '4px',
border: '1px solid #ddd',
fontSize: '1rem',
background: 'white',
minWidth: '150px'
}}
>
<option value="all">All Tasks</option>
<option value="finished">Completed</option>
<option value="unfinished">Pending</option>
</select>
</div>
{/* Todo List */}
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '10px'
}}>
{getFilteredSortedTodos().length === 0 ? (
<p style={{
textAlign: 'center',
padding: '20px',
color: '#666'
}}>
{searchTerm ? 'No matching tasks found' : 'No tasks yet. Add one above!'}
</p>
) : (
getFilteredSortedTodos().map(todo => (
<div
key={todo.id}
style={{
display: 'flex',
alignItems: 'center',
padding: '15px',
borderRadius: '8px',
background: todo.status === 'finished' ? '#e8f5e9' : '#fff3e0',
borderLeft: `4px solid ${todo.status === 'finished' ? '#4CAF50' : '#FF9800'}`,
gap: '15px',
transition: 'all 0.3s ease'
}}
>
<input
type="checkbox"
checked={todo.status === 'finished'}
onChange={() => toggleTodo(todo.id)}
style={{
width: '20px',
height: '20px',
cursor: 'pointer'
}}
/>
<div style={{ flex: 1 }}>
<p style={{
margin: '0',
textDecoration: todo.status === 'finished' ? 'line-through' : 'none',
color: todo.status === 'finished' ? '#666' : '#333',
fontSize: '1.1rem'
}}>
{todo.text}
</p>
<p style={{
margin: '5px 0 0',
fontSize: '0.9rem',
color: '#666'
}}>
Due: {new Date(todo.deadline).toLocaleString()}
{todo.finishedTime && (
<span style={{ marginLeft: '10px' }}>
Completed: {new Date(todo.finishedTime).toLocaleString()}
</span>
)}
</p>
</div>
<button
onClick={() => deleteTodo(todo.id)}
style={{
padding: '5px 10px',
borderRadius: '4px',
border: 'none',
background: '#f44336',
color: 'white',
cursor: 'pointer',
fontSize: '0.9rem'
}}
>
Delete
</button>
</div>
))
)}
</div>
{/* Stats */}
<div style={{
marginTop: '20px',
padding: '15px',
background: '#f5f5f5',
borderRadius: '8px',
textAlign: 'center'
}}>
<p style={{ margin: '0' }}>
{todos.filter(t => t.status === 'unfinished').length} pending,
{todos.filter(t => t.status === 'finished').length} completed •
Total: {todos.length} tasks
</p>
</div>
</div>
);
};
export default ToDoList;
This ToDoList component provides a complete solution with:
1. LocalStorage persistence for todos
2. CRUD operations (Create, Read, Update, Delete)
3. Search functionality
4. Filtering by status (all, finished, unfinished)
5. Sorting by deadline with unfinished items prioritized
6. Clean, responsive UI with:
- Add task form with text and deadline inputs
- Search bar and filter dropdown
- Visually distinct todo items with completion status
- Stats section showing counts
- Smooth transitions and intuitive interactions
The component is fully self-contained with all state management and UI rendering handled internally. The TypeScript interface ensures type safety for all todo operations.
</html>