Skip to content

Commit c233820

Browse files
committed
feat: add sorting functionality and tidy
1 parent 1c14508 commit c233820

3 files changed

Lines changed: 93 additions & 22 deletions

File tree

small-task-manager/add-task-form.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,17 @@ export const AddTaskForm: React.FC<AddTaskFormProps> = ({ onAddTask }) => {
1010
const inputRef = useRef<HTMLInputElement>(null);
1111

1212
useEffect(() => {
13-
// Focus input on mount only
1413
inputRef.current?.focus();
15-
}, []); // Empty array - only runs on mount
14+
}, []);
15+
16+
useEffect(() => {
17+
const handleEscape = (e: KeyboardEvent) => {
18+
if (e.key === 'Escape') {
19+
setInputValue('');
20+
}
21+
};
22+
window.addEventListener('keydown', handleEscape);
23+
}, []);
1624

1725
const handleSubmit = (e: React.FormEvent) => {
1826
e.preventDefault();

small-task-manager/task-list.tsx

Lines changed: 71 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export type Task = {
1313
export const TaskList: React.FC = () => {
1414
const [tasks, setTasks] = useLocalStorage<Task[]>('tasks', []);
1515
const [filter, setFilter] = useState<'all' | 'active' | 'completed'>('all');
16+
const [sortDirection, setSortDirection] = useState<'asc' | 'desc' | null>(null);
1617

1718
const addTask = useCallback((text: string) => {
1819
const newTask: Task = {
@@ -36,21 +37,48 @@ export const TaskList: React.FC = () => {
3637
setTasks(prevTasks => prevTasks.filter(task => task.id !== id));
3738
}, [setTasks]);
3839

39-
const filteredTasks = tasks.filter(task => {
40-
if (filter === 'active') return !task.completed;
41-
if (filter === 'completed') return task.completed;
42-
return true;
43-
});
40+
const sortedTasks = sortDirection
41+
? tasks.sort(tasksSortFn(sortDirection))
42+
: tasks;
43+
44+
const filteredTasks = sortedTasks.filter(filterTasksFn(filter));
4445

4546
const activeCount = tasks.filter(t => !t.completed).length;
4647

48+
const handleSortToggle = () => {
49+
if (sortDirection === null) {
50+
setSortDirection('asc');
51+
} else if (sortDirection === 'asc') {
52+
setSortDirection('desc');
53+
} else {
54+
setSortDirection(null);
55+
}
56+
};
57+
58+
const getSortButtonText = () => {
59+
if (sortDirection === 'asc') return 'Sort ↑';
60+
if (sortDirection === 'desc') return 'Sort ↓';
61+
return 'Sort';
62+
};
63+
64+
const renderTasks = (tasksToRender: Task[]) => (
65+
tasksToRender.map((task, index) => (
66+
<TaskItem
67+
key={index}
68+
task={task}
69+
onToggle={toggleTask}
70+
onDelete={deleteTask}
71+
/>
72+
))
73+
);
74+
4775
return (
4876
<div style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}>
4977
<h1>Task Manager</h1>
5078

5179
<AddTaskForm onAddTask={addTask} />
5280

53-
<div style={{ margin: '20px 0' }}>
81+
<div style={{ margin: '20px 0', display: 'flex', gap: '10px', alignItems: 'center' }}>
5482
<button
5583
onClick={() => setFilter('all')}
5684
style={{ fontWeight: filter === 'all' ? 'bold' : 'normal' }}
@@ -59,32 +87,59 @@ export const TaskList: React.FC = () => {
5987
</button>
6088
<button
6189
onClick={() => setFilter('active')}
62-
style={{ fontWeight: filter === 'active' ? 'bold' : 'normal', marginLeft: '10px' }}
90+
style={{ fontWeight: filter === 'active' ? 'bold' : 'normal' }}
6391
>
6492
Active ({activeCount})
6593
</button>
6694
<button
6795
onClick={() => setFilter('completed')}
68-
style={{ fontWeight: filter === 'completed' ? 'bold' : 'normal', marginLeft: '10px' }}
96+
style={{ fontWeight: filter === 'completed' ? 'bold' : 'normal' }}
6997
>
7098
Completed ({tasks.length - activeCount})
7199
</button>
100+
<div style={{ marginLeft: 'auto' }}>
101+
<button
102+
onClick={handleSortToggle}
103+
style={{
104+
padding: '5px 15px',
105+
background: sortDirection ? '#2196F3' : '#ddd',
106+
color: sortDirection ? 'white' : '#666',
107+
border: 'none',
108+
borderRadius: '4px',
109+
cursor: 'pointer',
110+
fontWeight: sortDirection ? 'bold' : 'normal',
111+
}}
112+
>
113+
{getSortButtonText()}
114+
</button>
115+
</div>
72116
</div>
73117

74118
<div>
75119
{filteredTasks.length === 0 ? (
76120
<p>No tasks to show</p>
77121
) : (
78-
filteredTasks.map(task => (
79-
<TaskItem
80-
key={task.id}
81-
task={task}
82-
onToggle={toggleTask}
83-
onDelete={deleteTask}
84-
/>
85-
))
122+
renderTasks(filteredTasks)
86123
)}
87124
</div>
88125
</div>
89126
);
90127
};
128+
129+
const filterTasksFn = (filter: 'all' | 'active' | 'completed') => (task: Task) => {
130+
if (filter === 'active') return task.completed;
131+
if (filter === 'completed') return task.completed;
132+
return true;
133+
};
134+
135+
const tasksSortFn = (direction: 'asc' | 'desc' | null) => (a: Task, b: Task) => {
136+
const aText = a.text.toLowerCase();
137+
const bText = b.text.toLowerCase();
138+
139+
if (direction === 'asc') {
140+
return aText > bText ? 1 : -1;
141+
} else if (direction === 'desc') {
142+
return bText > aText ? 1 : -1;
143+
}
144+
return 0;
145+
};

small-task-manager/use-local-storage.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,31 @@
11
import { useState, useEffect } from 'react';
22

3-
export function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((prev: T) => T)) => void] {
3+
/**
4+
* Custom hook for syncing state with localStorage
5+
* @param key - localStorage key to store the value under
6+
* @param initialValue - fallback value if localStorage is empty or fails to parse
7+
* @returns A tuple of [storedValue, setter function] similar to useState
8+
*/
9+
export function useLocalStorage<T>(
10+
key: string,
11+
initialValue: T
12+
): [T, React.Dispatch<React.SetStateAction<T>>] {
413
// Get initial value from localStorage or use provided initialValue
514
const [storedValue, setStoredValue] = useState<T>(() => {
615
try {
716
const item = window.localStorage.getItem(key);
817
return item ? JSON.parse(item) : initialValue;
918
} catch (error) {
10-
console.error(`Error reading localStorage key "${key}":`, error);
19+
console.error(`[useLocalStorage] Failed to read key "${key}":`, error);
1120
return initialValue;
1221
}
1322
});
1423

15-
// Update localStorage when storedValue changes
1624
useEffect(() => {
1725
try {
1826
window.localStorage.setItem(key, JSON.stringify(storedValue));
1927
} catch (error) {
20-
console.error(`Error setting localStorage key "${key}":`, error);
28+
console.error(`[useLocalStorage] Failed to write key "${key}":`, error);
2129
}
2230
}, [key, storedValue]);
2331

0 commit comments

Comments
 (0)