From c233820b11da5596d585179b27a75aaa3bdca65c Mon Sep 17 00:00:00 2001 From: Stuart Burrows Date: Fri, 28 Nov 2025 14:07:14 +0000 Subject: [PATCH] feat: add sorting functionality and tidy --- small-task-manager/add-task-form.tsx | 12 +++- small-task-manager/task-list.tsx | 87 ++++++++++++++++++++----- small-task-manager/use-local-storage.ts | 16 +++-- 3 files changed, 93 insertions(+), 22 deletions(-) diff --git a/small-task-manager/add-task-form.tsx b/small-task-manager/add-task-form.tsx index 6a6f3ef..3f0aa80 100644 --- a/small-task-manager/add-task-form.tsx +++ b/small-task-manager/add-task-form.tsx @@ -10,9 +10,17 @@ export const AddTaskForm: React.FC = ({ onAddTask }) => { const inputRef = useRef(null); useEffect(() => { - // Focus input on mount only inputRef.current?.focus(); - }, []); // Empty array - only runs on mount + }, []); + + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + setInputValue(''); + } + }; + window.addEventListener('keydown', handleEscape); + }, []); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); diff --git a/small-task-manager/task-list.tsx b/small-task-manager/task-list.tsx index 80204ab..4b126c5 100644 --- a/small-task-manager/task-list.tsx +++ b/small-task-manager/task-list.tsx @@ -13,6 +13,7 @@ export type Task = { export const TaskList: React.FC = () => { const [tasks, setTasks] = useLocalStorage('tasks', []); const [filter, setFilter] = useState<'all' | 'active' | 'completed'>('all'); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc' | null>(null); const addTask = useCallback((text: string) => { const newTask: Task = { @@ -36,21 +37,48 @@ export const TaskList: React.FC = () => { setTasks(prevTasks => prevTasks.filter(task => task.id !== id)); }, [setTasks]); - const filteredTasks = tasks.filter(task => { - if (filter === 'active') return !task.completed; - if (filter === 'completed') return task.completed; - return true; - }); + const sortedTasks = sortDirection + ? tasks.sort(tasksSortFn(sortDirection)) + : tasks; + + const filteredTasks = sortedTasks.filter(filterTasksFn(filter)); const activeCount = tasks.filter(t => !t.completed).length; + const handleSortToggle = () => { + if (sortDirection === null) { + setSortDirection('asc'); + } else if (sortDirection === 'asc') { + setSortDirection('desc'); + } else { + setSortDirection(null); + } + }; + + const getSortButtonText = () => { + if (sortDirection === 'asc') return 'Sort ↑'; + if (sortDirection === 'desc') return 'Sort ↓'; + return 'Sort'; + }; + + const renderTasks = (tasksToRender: Task[]) => ( + tasksToRender.map((task, index) => ( + + )) + ); + return (

Task Manager

-
+
+
+ +
{filteredTasks.length === 0 ? (

No tasks to show

) : ( - filteredTasks.map(task => ( - - )) + renderTasks(filteredTasks) )}
); }; + +const filterTasksFn = (filter: 'all' | 'active' | 'completed') => (task: Task) => { + if (filter === 'active') return task.completed; + if (filter === 'completed') return task.completed; + return true; +}; + +const tasksSortFn = (direction: 'asc' | 'desc' | null) => (a: Task, b: Task) => { + const aText = a.text.toLowerCase(); + const bText = b.text.toLowerCase(); + + if (direction === 'asc') { + return aText > bText ? 1 : -1; + } else if (direction === 'desc') { + return bText > aText ? 1 : -1; + } + return 0; +}; diff --git a/small-task-manager/use-local-storage.ts b/small-task-manager/use-local-storage.ts index a48de44..5b4cd86 100644 --- a/small-task-manager/use-local-storage.ts +++ b/small-task-manager/use-local-storage.ts @@ -1,23 +1,31 @@ import { useState, useEffect } from 'react'; -export function useLocalStorage(key: string, initialValue: T): [T, (value: T | ((prev: T) => T)) => void] { +/** + * Custom hook for syncing state with localStorage + * @param key - localStorage key to store the value under + * @param initialValue - fallback value if localStorage is empty or fails to parse + * @returns A tuple of [storedValue, setter function] similar to useState + */ +export function useLocalStorage( + key: string, + initialValue: T +): [T, React.Dispatch>] { // Get initial value from localStorage or use provided initialValue const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { - console.error(`Error reading localStorage key "${key}":`, error); + console.error(`[useLocalStorage] Failed to read key "${key}":`, error); return initialValue; } }); - // Update localStorage when storedValue changes useEffect(() => { try { window.localStorage.setItem(key, JSON.stringify(storedValue)); } catch (error) { - console.error(`Error setting localStorage key "${key}":`, error); + console.error(`[useLocalStorage] Failed to write key "${key}":`, error); } }, [key, storedValue]);